moment github地址html
在上一篇文章中,主要是對項目作了介紹,而且對系統分析和系統設計作了大概的介紹。那麼接下來這篇文章會對系統的實現作介紹,主要是選擇一些比較主要的模塊或者說可拿出來與你們分享的模塊。好了,接入正題吧~~node
服務端這邊使用的是Express框架,數據庫使用的是MongoDB,經過Mongoose模塊來操做數據庫。這邊主要是想下對MongoDB作個介紹,固然看官瞭解的話直接往下劃~~~~~~ios
在項目開始前要確保電腦是否安裝mongoDB,下載點我,圖像化工具Robo 3T 點我,下載好具體怎麼配置還請問度娘或Google吧,本文不作介紹了哈。注意:安裝完mongoDB的時候進行項目時要把lib目錄下的mongod服務器打開哈~~git
MongoDB 是一個基於分佈式文件存儲的數據庫,是一個介於關係型數據庫和非關係型數據庫之間的開源產品,它是功能最爲豐富的非關係型數據庫,也是最像關係型數據庫的。可是和關係型數據庫不一樣,MongoDB沒有表和行的概念,而是一個面向集合、文檔
的數據庫。其中的文檔是一個鍵值對,採用BSON(Binary Serialized Document Format),BSON是一種相似於JSON的二進制形式的存儲格式,而且BSON具備表示數據類型的擴展,所以支持的數據很是豐富。MongoDB有兩個很重要的數據類型就是內嵌文檔和數組
,並且在數組內能夠嵌入其餘文檔,這樣一條記錄就能表示很是複雜的關係。github
Mongoose是在node.js異步環境下對MongoDB進行簡便操做的對象模型工具,能從數據庫提取任何信息,能夠用面向對象的方法來讀寫數據,從而使操做MongoDB數據庫很是便捷。Mongoose中有三個很是重要的概念,即是Schema(模式),Model(模型),Entity(實體)。web
//Schema const mongoose = require('mongoose'); const Schema = mongoose.Schema; const UserSchema = new Schema({ token: String, is_banned: {type: Boolean, default: false}, //是否禁言 enable: { type: Boolean, default: true }, //用戶是否有效 is_actived: {type: Boolean, default: false}, //郵件激活 username: String, password: String, email: String, //email惟一性 code: String, email_time: {type: Date}, phone: {type: String}, description: { type: String, default: "這我的很懶,什麼都沒有留下..." }, avatar: { type: String, default: "http://p89inamdb.bkt.clouddn.com/default_avatar.png" }, bg_url: { type: String, default: "http://p89inamdb.bkt.clouddn.com/FkagpurBWZjB98lDrpSrCL8zeaTU"}, ip: String, ip_location: { type: Object }, agent: { type: String }, // 用戶ua last_login_time: { type: Date }, ..... }); 複製代碼
//生成一個具體User的model並導出 const User = mongoose.model("User", UserSchema); //第一個參數是集合名,在數據庫中會把Model名字字母所有變小寫和在後面加複數s //執行到這個時候你的數據庫中就有了 users 這個集合 module.exports = User; 複製代碼
const newUser = new UserModel({ //UserModel 爲導出來的 User
email: req.body.email,
code: getCode(),
email_time: Date.now()
});
複製代碼
Mongoose中有一個東西我的感受很是主要,那即是populate
,經過populate他能夠很方便的與另外一個集合創建關係。以下,user集合能夠與article集合、user集合自己進行關聯,根據其內嵌文檔的特性,這樣子他即可之內嵌子文檔,子文檔中有能夠內嵌子文檔,這樣子它返回的數據就會異常的豐富。
const user = await UserModel.findOne({_id: req.query._id, is_actived: true}, {password: 0}).populate({ path: 'image_article', model: 'ImageArticle', populate: { path: 'author', model: 'User' } }).populate({ path: 'collection_film_article', model: 'FilmArticle', }).populate({ path: 'following_user', model: 'User', }).populate({ path: 'follower_user', model: 'User', }).exec(); 複製代碼
服務端主要是操做數據庫,對數據庫進行增刪改查(CRUD)等操做。項目中的接口,Mongoose的各類方法這邊就不對其作詳細介紹,你們能夠查看Mongoose文檔。
本系統的用戶身份認證機制採用的是JSON Web Token(JWT)
,它是一種輕量的認證規範,也用於接口的認證。咱們知道,HTTP協議是一種無狀態的協議,這便意味着每一個請求都是獨立的,當用戶提供了用戶名和密碼來對咱們的應用進行用戶認證,那麼在下一次請求的時候,用戶須要再進行一次用戶的認證才能夠,由於根據HTTP協議,咱們並不能知道是哪一個用戶發出的請求,本系統採用了token的鑑權機制。這個token必需要在每次請求時傳遞給服務端,它應該保存在請求頭裏,另外,服務端要支持CORS(跨來源資源共享)策略,通常咱們在服務端這麼作就能夠了Access-Control-Allow-Origin: *。
在用戶身份認證這一塊有不少方法,最多見的像cookie ,session。那麼他們三之間又有什麼區別,這裏有兩篇文章介紹的挺全面。
token 與 session的區別在於,它不一樣於傳統的session認證機制,它不須要在服務端去保留用戶的認證信息或其會話的信息。系統一旦比較大,都會採用機器集羣來作負載均衡,這須要多臺機器,因爲session是保存在服務端,那麼就要 去考慮用戶究竟是在哪一臺服務器上進行登陸的,這即是一個很大的負擔。
那麼就有人想問了,你這個系統這麼小,爲何不使用傳統的session機制呢?哈~由於以前本身的項目通常都是使用session作登陸,沒使用過token,想嘗試嘗試入入坑~~哈哈哈~
JWT主要的實現思路以下:
在用戶登陸成功的時候建立token保存於數據庫中,並返回給客戶端。
客戶端以後的每一次請求都要帶上token,在請求頭裏加入Authorization,並加上token.
在服務端進行驗證token的有效性,在有效期內返回200狀態碼,token過時則返回401狀態碼
以下圖所示:
JWT請求圖在node中主要用了jsonwebtoken
這個模塊來建立JWT,jsonwebtoken的使用請查看jsonwebtoken文檔。項目中建立token的中間件createToken以下
/**
* createToken.js
*/
const jwt = require('jsonwebtoken'); // 引入jsonwebtoken模塊
const secret = '我是密鑰'
//登陸時:覈對用戶名和密碼成功後,應用將用戶的id(user_id)做爲JWT Payload的一個屬性
module.exports = function(user_id){
const token = jwt.sign({
user_id: user_id
}, secret, { //密鑰
expiresIn: '24h' //過時時間設置爲24h。那麼decode這個token的時候獲得的過時時間爲:建立token的時間+設置的值
});
return token;
};
複製代碼
return 出來的 token 相似eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoiYWRtaW4iLCJpYXQiOjE1MzQ2ODQwNzAsImV4cCI6MTUzNDc3MDQ3MH0.Y3kaglqW9Fpe1YxF_uF7zwTV224W4W97MArU0aI0JgM
。咱們仔細看這字符串,分爲三段,分別被 "." 隔開。如今咱們分別對前兩段進行base64解碼以下:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 ===> {"alg":"HS256","typ":"JWT"} 其中 alg是加密算法名字,typ是類型 eyJ1c2VyX2lkIjoiYWRtaW4iLCJpYXQiOjE1MzQ2ODQwNzAsImV4cCI6MTUzNDc3MDQ3MH0 ===> {"user_id":"admin","iat":1534684070,"exp":1534770470} 其中 name是咱們儲存的內容,iat建立的時間戳,exp到期時間戳。 Y3kaglqW9Fpe1YxF_uF7zwTV224W4W97MArU0aI0JgM ===> 最後一段是由前面兩段字符串,HS256加密後獲得。因此前面的任何一個字段修改,都會致使加密後的字符串不匹配。 複製代碼
當咱們根據用戶的id建立獲取到token以後,咱們須要把token返回到客戶端,客戶端對其在本地(localStorage)保存, 客戶端以後的每一次請求都要帶上token,在請求頭裏加入Authorization,並加上token,服務端進行驗證token的有效性。那麼咱們如何驗證token的有效性呢? 因此咱們須要checkToken這個中間件來檢測token的有效性。
/**
* checkToken
*/
const jwt = require('jsonwebtoken');
const secret = '我是密鑰'
module.exports = async ( req, res, next ) => {
const authorization = req.get('Authorization');
if (!authorization) {
res.status(401).end(); //接口須要認證可是有沒帶上token,返回401未受權狀態碼
return
}
const token = authorization.split(' ')[1];
try {
let tokenContent = await jwt.verify(token, secret); //若是token過時或驗證失敗,將拋出錯誤
next(); //執行下一個中間件
} catch (err) {
console.log(err)
res.status(401).end(); //token過時或者驗證失敗返回401狀態碼
}
}
複製代碼
那麼如今我們只要在須要用戶認證的接口上,在操做數據以前,加上checkToken中間件便可,以下調用:
//更新用戶信息 router.post('/updateUserInfo', checkToken, User.updateUserInfo) //若是checkToken檢測不成功,它便返回401狀態碼,不會對User.updateUserInfo作任何操做, 只有檢測token成功,才能處理User.updateUserInfo 複製代碼
咱們如何保證每次請求都能在請求頭裏加入Authorization,並加上token,這就要用到Axios的請求攔截,而且也用到了它的響應攔截,由於在服務端返回401狀態碼以後應要執行登出操做,清楚本地token的存儲,具體代碼以下:
//request攔截器 instance.interceptors.request.use( config => { //每次發送請求以前檢測本地是否存有token,都要放在請求頭髮送給服務器 if(localStorage.getItem('token')){ if (config.url.indexOf('upload-z0.qiniup.com/putb64') > -1){ config.headers.Authorization = config.headers['UpToken']; //加上七牛雲上傳token } else { config.headers.Authorization = `token ${localStorage.getItem('token')}`.replace(/(^\")|(\"$)/g, ''); //加上系統接口token } } console.log('config',config) return config; }, err => { console.log('err',err) return Promise.reject(err); } ); //response攔截器 instance.interceptors.response.use( response => { return response; }, error => { //默認除了2XX以外的都是錯誤的,就會走這裏 if(error.response){ switch(error.response.status){ case 401: console.log(error.response) store.dispatch('ADMIN_LOGINOUT'); //多是token過時,清除它 router.replace({ //跳轉到登陸頁面 path: '/login', query: { redirect: '/dashboard' } // 將跳轉的路由path做爲參數,登陸成功後跳轉到該路由 }); } } return Promise.reject(error.response); } ); 複製代碼
其中的if else 是由於本系統的圖片,音視頻是放在七牛雲,上傳須要七牛雲上傳base64圖片的時候token是放在請求頭的,正常的圖片上傳不是放在請求頭,因此這邊對token作了區分,如何接入七牛雲也會在下面模塊介紹到。
本系統的圖片,音視頻是放在七牛雲,因此須要接入七牛雲。七牛雲分了兩種狀況,正常圖片和音視頻的上傳和base64圖片的上傳,由於七牛雲在對他們二者上傳的Content-Type
和domain(域)
有所不一樣,正常圖片和音視頻的Content-Type是headers: {'Content-Type':'multipart/form-data'}
domain是domain='https://upload-z0.qiniup.com'
,而base64圖片的上傳則是headers:{'Content-Type':'application/octet-stream'}
domain是domain='https://upload-z0.qiniup.com/putb64/-1'
,因此他們請求的時候token放的地方不一樣,base64就像上面所說的放在請求頭Authorization
中,而正常的放在form-data
中。在服務端經過接口請求來獲取七牛雲上傳token,客戶端獲取到七牛雲token,經過不一樣方案將token帶上。
headers:{'Content-Type':'application/octet-stream'}
和 domain='https://upload-z0.qiniup.com/putb64/-1'
,token放在請求頭Authorization
中。headers: {'Content-Type':'multipart/form-data'}
和domain='https://upload-z0.qiniup.com'
,token 放在 form-data
中。服務端經過qiniu
這個模塊進行建立token,服務端代碼以下:
/** * 構建一個七牛雲上傳憑證類 * @class QN */ const qiniu = require('qiniu') //導入qiniu模塊 const config = require('../config') class QN { /** * Creates an instance of qn. * @param {string} accessKey -七牛雲AK * @param {string} secretKey -七牛雲SK * @param {string} bucket -七牛雲空間名稱 * @param {string} origin -七牛雲默認外鏈域名,(可選參數) */ constructor (accessKey, secretKey, bucket, origin) { this.ak = accessKey this.sk = secretKey this.bucket = bucket this.origin = origin } /** * 獲取七牛雲文件上傳憑證 * @param {number} time - 七牛雲憑證過時時間,以秒爲單位,若是爲空,默認爲7200,有效時間爲2小時 */ upToken (time) { const mac = new qiniu.auth.digest.Mac(this.ak, this.sk) const options = { scope: this.bucket, expires: time || 7200 } const putPolicy = new qiniu.rs.PutPolicy(options) const uploadToken = putPolicy.uploadToken(mac) return uploadToken } } exports.QN = QN; exports.upToken = () => { return new QN(config.qiniu.accessKey, config.qiniu.secretKey, config.qiniu.bucket, config.qiniu.origin).upToken() //每次調用都建立一個token } 複製代碼
//獲取七牛雲token接口 const {upToken} = require('../utils/qiniu') app.get('/api/uploadToken', (req, res, next) => { const token = upToken() res.send({ status: 1, message: '上傳憑證獲取成功', upToken: token, }) }) 複製代碼
因爲正常圖片和音視頻的上傳和base64圖片的上傳,由於七牛雲在對他們二者上傳的Content-Type
和domain(域)
有所不一樣,因此的token請求存放的位置有所不一樣,所以要區分,客戶端調用上傳代碼以下:
//根據獲取到的上傳憑證uploadToken上傳文件到指定域 //正常圖片和音視頻的上傳 uploadFile(formdata, domain='https://upload-z0.qiniup.com',config={headers:{'Content-Type':'multipart/form-data'}}){ console.log(domain) console.log(formdata) return instance.post(domain, formdata, config) }, //base64圖片的上傳 //根據獲取到的上傳憑證uploadToken上傳base64到指定域 uploadBase64File(base64, token, domain = 'https://upload-z0.qiniup.com/putb64/-1', config = { headers: { 'Content-Type': 'application/octet-stream', }, }){ const pic = base64.split(',')[1]; config.headers['UpToken'] = `UpToken ${token}` return instance.post(domain, pic, config) }, 複製代碼
function upload(Vue, data, callbackSuccess, callbackFail) { //獲取上傳token以後處理 Vue.prototype.axios.getUploadToken().then(res => { if (typeof data === 'string'){ //若是是base64 const token = res.data.upToken Vue.prototype.axios.uploadBase64File(data, token).then(res => { if (res.status === 200){ callbackSuccess && callbackSuccess({ data: res.data, result_url: `http://p89inamdb.bkt.clouddn.com/${res.data.key}` }) } }).catch((error) => { callbackFail && callbackFail({ error }) }) } else if (data instanceof FormData){ //若是是FormData data.append('token', res.data.upToken) data.append('key', `moment${Date.now()}${Math.floor(Math.random() * 100)}`) Vue.prototype.axios.uploadFile(data).then(res => { if (res.status === 200){ callbackSuccess && callbackSuccess({ data: res.data, result_url: `http://p89inamdb.bkt.clouddn.com/${res.data.key}` }) } }).catch((error) => { callbackFail && callbackFail({ error }) }) } else { const formdata = new FormData() //若是不是formData 就建立formData formdata.append('token', res.data.upToken) formdata.append('file', data.file || data) formdata.append('key', `moment${Date.now()}${Math.floor(Math.random() * 100)}.${data.file.type.split('/')[1]}`) // 獲取到憑證以後再將文件上傳到七牛雲空間 console.log('formdata',formdata) Vue.prototype.axios.uploadFile(formdata).then(res => { console.log('res',res) if (res.status === 200){ callbackSuccess && callbackSuccess({ data: res.data, result_url: `http://p89inamdb.bkt.clouddn.com/${res.data.key}` //返回的圖片連接 }) } }).catch((error) => { console.log(error) callbackFail && callbackFail({ error }) }) } }) } export default upload 複製代碼
系統的後臺管理面向的是合做做者和管理員,涉及到兩種角色,故此要作權限管理。不一樣的權限對應着不一樣的路由,同時側邊欄的菜單也需根據不一樣的權限,異步生成,不一樣於以往的服務端直接返回路由表,由前端動態生成,接下來介紹下登陸和權限驗證的思路:
登陸:當用戶填寫完帳號和密碼後向服務端驗證是否正確,驗證經過以後,服務端會返回一個token,拿到token以後前端會根據token再去拉取一個getAdminInfo的接口來獲取用戶的詳細信息(如用戶權限,用戶名等等信息)。
權限驗證:經過token獲取用戶對應的role,動態根據用戶的role算出其對應有權限的路由,經過vue-router的beforeEach進行全局前置守衛再經過router.addRoutes動態掛載這些路由。
代碼有點多,這邊就直接放流程圖哈~~
權限路由流程圖最近正好也在公司作中後臺項目,公司的中後臺項目的這邊是由服務端生成路由表,前端進行直接渲染,畢竟公司的一整套業務比較成熟。可是咱們會在想能不能由前端維護路由表,這樣不用到時候項目迭代,前端每增長頁面都要讓服務端兄弟配一下路由和權限,固然前提多是項目比較小的時候。
帳號模塊是業務中最爲基礎的模塊,承擔着整個系統全部的帳號相關的功能。系統實現了用戶註冊、用戶登陸、密碼修改、找回密碼功能。
系統的帳號模塊使用了郵件服務,針對普通用戶的註冊採用了郵件服務來發送驗證碼,以及密碼的修改等操做都採用了郵件服務。在node.js中主要採用了Nodemailer,Nodemailer是一個簡單易用的Node.js郵件發送組件,它的使用能夠摸我摸我摸我,經過此模塊進行郵件的發送。大家可能會問,爲何不用短信服務呢?哈~由於短信服務要錢,哈哈哈
/* * email 郵件模塊 */ const nodemailer = require('nodemailer'); const smtpTransport = require('nodemailer-smtp-transport'); const config = require('../config') const transporter = nodemailer.createTransport(smtpTransport({ host: 'smtp.qq.com', secure: true, port: 465, // SMTP 端口 auth: { user: config.email.account, pass: config.email.password //這裏密碼不是qq密碼,是你設置的smtp受權碼 } })); let clientIsValid = false; const verifyClient = () => { transporter.verify((error, success) => { if (error) { clientIsValid = false; console.warn('郵件客戶端初始化鏈接失敗,將在一小時後重試'); setTimeout(verifyClient, 1000 * 60 * 60); } else { clientIsValid = true; console.log('郵件客戶端初始化鏈接成功,隨時可發送郵件'); } }); }; verifyClient(); const sendMail = mailOptions => { if (!clientIsValid) { console.warn('因爲未初始化成功,郵件客戶端發送被拒絕'); return false; } mailOptions.from = '"ShineTomorrow" <admin@momentin.cn>' transporter.sendMail(mailOptions, (error, info) => { if (error) return console.warn('郵件發送失敗', error); console.log('郵件發送成功', info.messageId, info.response); }); }; exports.sendMail = sendMail; 複製代碼
帳號的註冊先是填寫email,填寫好郵箱以後會經過Nodemailer發送一封含有有效期的驗證碼郵件,以後填寫驗證碼、暱稱和密碼便可完成註冊,而且爲了安全考慮,對密碼採用了安全哈希算法(Secure Hash Algorithm)進行加密。帳號的登陸以帳號或者郵箱號加上密碼進行登陸,而且採用上文所說的JSON Web Token(JWT)身份認證機制,從而實現用戶和用戶登陸狀態數據的對應。
個人郵件長這樣👆(可本身寫郵件模板)當用戶被人關注、評論被他人回覆和點贊等一些社交性的操做的時候,在數據存儲完成後,服務端應須要及時向用戶推送消息來提醒用戶。消息推送模塊採用了Socket.io
來實現,socket.io封裝了websocket,不支持websocket的狀況還提供了降級AJAX輪詢,功能完備,設計優雅,是開發實時雙向通信的不二手段。
經過 socket.io,用戶每打開一個頁面,這個頁面都會和服務端創建一個鏈接。在服務端能夠經過鏈接的socket的id屬性來匹配到一個創建鏈接的頁面。因此用戶的ID和socket的id,是一對多的關係,即一個用戶可能在登陸後打開多個頁面。而socket.io沒有提供從服務端向某個用戶單獨發送消息的功能,更沒有提供向某個用戶打開的全部頁面推送消息的功能。可是socket.io提供了room的概念,即羣組。在創建websocket時,客戶端能夠選擇加入某個room,若是這個room沒有存在則自動新建一個,不然直接加入,服務端能夠向某個room中的全部客戶端推送消息。
根據這個特性,設計將用戶的ID做爲room的名字,當某個用戶打開頁面創建鏈接時,會選擇加入以本身用戶ID爲名字的room。這樣,在用戶ID爲名字的 room中,加入的都是用戶本身打開的頁面創建的鏈接。從而向某個用戶推送消息,能夠直接經過向以此用戶的ID爲名字的room發送消息,這樣就會推送到用戶打開的全部頁面。
有了想法後咱們就開始魯吧~,在服務端中socket.io
在客戶端中使用vue-socket.io
, 服務端代碼以下:
/* * app.js中 */ const server = require('http').createServer(app); const io = require('socket.io')(server); global.io = io; //全局設上io值, 由於在其餘模塊要用到 io.on('connection', function (socket) { // setTimeout(()=>{ // socket.emit('nodeEvent', { hello: 'world' }); // }, 5000) socket.on('login_success', (data) => { //接受客戶端觸發的login_success事件 //使用user_id做爲房間號 socket.join(data.user_id); console.log('login_success',data); }); }); io.on('disconnect', function (socket) { socket.emit('user disconnected'); }); server.listen(config.port, () => { console.log(`The server is running at http://localhost:${config.port}`); }); 複製代碼
/* * 某業務模塊 */ //例如某文章增長評論 io.in(newMusicArticle.author.user_id._id).emit('receive_message', newMessage); //實時通知客戶端receive_message事件 sendMail({ //發送郵件 to: newMusicArticle.author.user_id.email, subject: `Moment | 你有未讀消息哦~`, text: `啦啦啦,我是賣報的小行家~~ 🤔`, html: emailTemplate.comment(sender, newMusicArticle, content, !!req.body.reply_to_id) }) 複製代碼
客服端代碼:
<script> export default { name: 'App', data () { return { } }, sockets:{ connect(){ }, receive_message(val){ //接受服務端觸發的事件,進行客戶端實時更新數據 if (val){ console.log('服務端實時通訊', val) this.$notify(val.content) console.log('this method was fired by the socket server. eg: io.emit("customEmit", data)') } } }, mixins: [mixin], mounted(){ if (!!JSON.parse(window.localStorage.getItem('user_info'))){ this.$socket.emit('login_success', { //通知服務端login_success 事件, 傳入id user_id: JSON.parse(window.localStorage.getItem('user_info'))._id }) } }, } </script> 複製代碼
評論模塊是爲了移動端WebApp下的文章下爲用戶提供關於評論的一些操做。系統實現了對文章的評論,評論的點贊功能,熱門評論置頂以及評論的回覆功能。在評論方面存在着各類各樣的安全性問題,好比XSS攻擊(Cross Site Scripting,跨站腳本攻擊)以及敏感詞等問題。預防XSS攻擊使用了xss
模塊, 敏感詞過濾使用text-censor
模塊。
在開發的時候常常會遇到這個問題,接口數據問題。有時候服務端返回的數據並非咱們想要的數據,前端要對數據進行再一步的處理。
例如服務端返回的某個字段爲null或者服務端返回的數據結構太深,前端須要不斷去判斷數據結構是否真的返回了正確的東西,而不是個null 或者undefined~
咱們前端都要這麼去處理過濾:
<div class="author"> 文 / {{(musicArticleInfo.author && musicArticleInfo.author.user_id) ? musicArticleInfo.author.user_id.username : '我叫這個名字'}} </div> 複製代碼
這就引出了一個思考:
對數據的進一步封裝處理,必然渲染性能方面會存在問題,並且咱們要時刻擔憂數據返回的問題。若是應用到公司的業務,咱們應該如何處理呢 ?
首屏渲染問題一直是單頁應用的痛點,那麼除了經常使用的性能優化,咱們還有什麼方法優化的嗎 ? 這個項目雖然面向的是移動端用戶,可能不存在SEO問題,若是作成pc端的話,像文章這類的應用,SEO都是必須品。
對於上面提出的問題,node的出現讓咱們看到了解決方案,那就常說的Node中間層,固然本項目中是不存在Node中間層,而是直接做爲後端語言處理數據庫。
因爲大部分的公司後端要麼是php要麼是java,通常不把node直接做爲後端語言,若是有使用到node,通常是做爲一箇中間層的形式存在。
對於第一個問題的解決:咱們能夠在中間層作接口轉發,在轉發的過程當中作數據處理。而不用擔憂數據返回的問題。
對於第二個問題的解決:有了Node中間層的話,那麼咱們能夠把首屏渲染的任務交給nodejs去作,次屏的渲染依然走以前的瀏覽器渲染。
有Node中間層的話,新的架構以下:
先後端的職能:
已經畢業一段時間了,寫文章是爲了回顧。本人水平通常,見諒見諒。這個產品的實現,一我的扛,在其中充當了各類角色,要有一點點產品思惟,要有一點點設計的想法,要會數據庫設計,要會後端開發,挺繁瑣的。最難的點我的感受仍是數據庫設計,數據庫要一開始就要設計的很完整,否則到後面的添添補補,就會很亂很亂,固然這個基礎是產品要很是清晰,剛開始本身心中對產品多是個模糊的定義,想一想差很少是那樣,因而乎就開始搞~~致使於後面數據庫設計的不是很滿意。因爲時間關係,如今的產品中有些小模塊還沒完成,可是大部分的功能結構已經完成,算是個成型的產品,固然是一個沒有通過測試的產品哈哈哈哈,要是有測試的話,那就哈哈哈哈你懂得 ~~~。
前路漫漫,吾將上下而求索~
完
謝謝~~