前后端分離 Vue + NodeJS(Koa) + MongoDB,從產品到開發(fā),全棧實踐

項目簡介

以下是項目地址,希望給個 star,鼓勵一下:
前端 gitHub 地址

后端 gitHub 地址

PS: 數據庫我放在了后端項目的 db-daike 目錄下

項目名:《代課》

介紹:大學期間加入了兩個比較大的社團,雖然已經畢業(yè)多年(這個夏天剛好一年哈哈),社團的群里有很多學弟學妹經常發(fā)一些幫忙代課的信息,并且也會附帶一些好處等等...都是這么過來的,確實比較了解有的課程老師就愛點名,三次就掛科(我沒有說《毛概》),好吧扯遠了!大概需求就是這樣的...我寫這個項目的原因就是為了實現這樣一個目的...

效果預覽:

one-c.gif
two-c.gif

這個就是我們最終做出來的樣子,那么開始干正事之前我們還是先捋一捋技術棧吧,說一說都用到了那些東西

技術棧

主要還是分為三大塊:前端(Vue) + 后端(NodeJS - Koa)+ 數據庫(MongoDB)

前端

主要是基于 Vue 全家桶,Vuex + axios

UI 框架隨便選了一個,對!就是這么隨意: Vant

css采用 scss

登錄頁的logo也是用的一個網站在線制作的,不好意思,網址我給忘了

項目結構(大致?)如下:

├── axios         // 對 axios進行 二次封裝
│   └── interface // api 文件目錄
├── src
│   ├── router    // 路由配置     
│   └── views     // 路由頁面
└── vuex          // 全局的狀態(tài)
    └── views     // 按路由模塊進行狀態(tài)分組

Tip: 結構中很多部分我省略了,一部分是屬于 vue 全家桶的就沒必要贅述了,另一部分比如請求接口和 Vuex 的模塊文件,之后會有講到

后端

后端采用 NodeJS,框架采用的是 Koa

其它就是在項目中使用的第三方庫,這里理列一下吧:

// ...
// 在項目中使用的文件中,我都有寫這些庫的npm或git地址方便學習
{
  "dependencies": {
    // 加密用戶密碼(數據庫沒有存明文密碼)
    "bcrypt": "^3.0.0",   
    // 解析前段請求參數     
    "koa-bodyparser": "^4.2.1",
    // 路由
    "koa-router": "^7.4.0",
    // 解決跨域
    "koa2-cors": "^2.0.6",
    // 操作 mongoDB 數據庫
    "mongoose": "^5.2.7",
    // 生成唯一 id
    "uuid": "^3.3.2"
  }
}
// ...

后端的項目結構有必要說一些,這個是我參考一些比較規(guī)范的項目自己搞的,也是比較隨意了,哈哈(我也是第一次這么搞):

├── app         // 對 axios進行 二次封裝
│   └── controllers // 控制器文件目錄,用來操作數據庫
│   │   └──  ...  // 對應操作的表,這里就省略了
│   ├── middleware// 自定義中間件目錄
│   ├── models    // 定義的表結構
│   │   └──  ...  // 對應的表,這里就省略了
│   └── utils     // 工具模塊目錄
│   │   └──  ...  // 工具模塊,這里就省略了
├── rotes         // 路由文件
│   ├── router    // 路由配置     
│   └── views     // 路由頁面
└── vuex          // 全局的狀態(tài)
    └── views     // 按路由模塊進行狀態(tài)分組
├── app.js        // 項目入口文件
└── config.js     // 配置文件

數據庫

依稀還得大學的時候學過 SQL,不行了畢業(yè)太久忘了,所以這里使用 MongoDB,也不做過多介紹,也沒啥好說了,安裝好了,增刪改查...剩下的就是提高了!這里補充一下我用的可視化工具是 Robo 3T

如果你還不了解 MongoDB 的話,我這里簡單寫了一下如何安裝使用 MongoDB

這里還是看一下幾張主要的表都長啥樣吧:

const CourseSchema = new Schema({
  id: {
    type: String,
    unique: true,
    required: true
  },
  status: {
    type: String
  },
  publisher: {
    type: String,
    required: true
  },
  publisherHeader: {
    type: String
  },
  publisherName: {
    type: String
  },
  studentId: {
    type: String
  },
  schoolId: {
    required: true,
    type: String
  },
  school: {
    type: String
  },
  phone: {
    type: String
  },
  publishTime: {
    type: String
  },
  closeTime: {
    type: String
  },
  remark: {
    type: String
  },
  receiver: {
    type: String
  },
  receiverName: {
    type: String
  },
  province: {
    type: Number
  },
  college: {
    type: String
  },
  major: {
    type: String
  },
  courseName: {
    type: String
  },
  courseTime: {
    required: true,
    type: String
  },
  courseClass: {
    type: String
  },
  coursePlace: {
    required: true,
    type: String
  },
  reward: {
    type: Number
  },
  hasName: {
    type: Boolean
  },
  hasStuId: {
    type: Boolean
  },
  hasPhone: {
    type: Boolean
  },
  hasReward: {
    type: Boolean
  }
}, { collection: 'courses', versionKey: false});

Tip: 算了算了,有點占地方,這里就看一張表吧,其它的在項目的 models 文件目錄下有

再提醒一下:數據庫我導出放在了后端項目的 db-daike 目錄下

工具和項目結構咱們都搞完了,就開始寫代碼吧

打通前后端


咋們這個項目采用前后端分離的方式進行開發(fā),為了開發(fā)的順暢進行,我們先調試一下:前端發(fā)個請求,后端接收消息,并從數據庫中拿到數據響應給前端(我們以post和get方法為例寫兩個接口),get請求獲取數據,post請求插入一條數據;

想一下這里主要的問題應該就是跨域的問題了!再仔細一想,跨域也不能算什么問題吧...哈哈(強行有問題)廢話不多說,開始:

  1. 連接數據庫

啟動一個 Node 服務連接數據庫,后續(xù)的操作都是基于數據庫的:

const Koa = require('koa');

// 這里是一些常量的配置文件
const config = require('./config');

const mongoose = require('mongoose');

const app = new Koa();

mongoose.connect(config.db, { useNewUrlParser: true }, err => {
  if (err) {
    console.error('Failed to connect to database');
  } else {
    console.log('Connecting database successfully');
  }
});

app.listen(config.port);

Tip: 啟動后服務 node app.js 之后看到如圖所示的打印就說明數據庫連接成功了:

connect.png

  1. 新建一張表,就叫 example

定義一下表結構,為了演示,我們就定義為只有一個類型為 String 類型的字段:

在后端項目 models 目錄下新建一個 example.js 文件來定義表結構;

const mongoose = require('mongoose');
// 這里的流程官網上有,講的很清楚,每一步是干什么的
const Schema = mongoose.Schema;
const exampleSchema = new Schema({
  msg: {
    type: String,
    required: true
  },
}, { 
  collection: 'example', // 這里是為了避免新建的表會帶上 s 后綴
  versionKey: false // 不需要 __v 字段,默認是加上的
});

module.exports = mongoose.model('example', exampleSchema);

這里我們先插入一條數據吧,這里為了方便,我直接使用前面提到的可視化工具 Robo 3T 插入一條 'Hello World' 數據:

insert.png
  1. 編寫對應 example 表的控制器,用來暴露接口

controllers 目錄下新建一個 example_controller.js

// 引入剛才定義的表
const Example_col = require('./../models/example');

// get 請求返回所有數據
const getExample = async (ctx, next) => {
  const req = ctx.request.body;

  const examples = await Example_col.find({}, { _id: 0 });

  ctx.status = 200;
  ctx.body = {
    msg: 'get request!!',
    data: {
      data: req,
      examples,
    }
  }
}

// post 帶一個 msg 參數,并插入數據庫
const postExample = async (ctx, next) => {
  const req = ctx.request.body;

  ctx.status = 200;
  if (!req.msg || typeof req.msg != 'string') {
    ctx.status = 401;
    ctx.body = {
      msg: 'post request!!',
      desc: `parameter error??!msg: ${req.msg}`,
      data: req
    }
    return;
  }

  const result = await Example_col.create({msg: req.msg});

  ctx.body = {
    msg: 'post request!!',
    desc: 'insert success!',
    data: result
  }
}

// 暴露出這兩個方法,在路由中使用
module.exports = {
  getExample,
  postExample
}
  1. 編寫對應的路由模塊

routes/api 目錄下新建一個 example_router.js 文件,主要的作用就是定義接口的請求路徑和方式:

// 引入路由模塊并實例化
const Router = require('koa-router');
const router = new Router();
// 導如對應的控制器
const example_controller = require('./../../app/controllers/example_controller');

// 為控制器的方法定義請求路徑和請求方式
router.get('/example/get', example_controller.getExample);
router.post('/example/post', example_controller.postExample);

module.exports = router;
  1. 作為中間件在入口文件中使用

這個概念我們就不多說了,這里把 Koa 的地址放在這吧

在入口文件 app.js 中增加兩句話:

const example_router = require('./routes/api/example_router');

app.use(example_router.routes()).use(example_router.allowedMethods());

重新啟動服務 node app.js

  1. 前端請求接口

這里就不多說了,有興趣的可以直接看倉庫的代碼就好了,大致是兩個接口有興趣的可以看看我前面寫的文章二次封裝axios:

const getExample = params => {
  return axios({
    url: '/example/get',
    method: 'get',
    params
  })
}

const postExample = data => {
  return axios({
    url: '/example/post',
    method: 'post',
    data
  })
}

為了方便展示結果,我直接在 前端的入口文件 App.vue 的生命周期鉤子中使用:

// ... 此處代碼省略
mounted() {
  this.$http.getExample({name: 'frank'});
  }

啟動前端項目: npm start

acao.png

哎喲!報錯了,不要慌仔細看看原來是跨域了呀,還記得前面說的強行有問題嗎?我前面有說到在依賴的三方庫里有一個叫 koa2-cors ,是時候該它上場了,我們在 app.js 中作為中間件使用它:

  1. 后端解決跨域

解決跨域的方式有很多,但這不是我們現在討論的重點,這里我使用上述的 koa2-cors,只需要將其作為中間件使用就好了,在 app.js 中添加:

const cors = require('koa2-cors');
app.use(cors());

PS: 這里要注意一下,js 是單線程語言,中間件是有執(zhí)行先后順序的,所以 app.use(cors()); 的使用必須在 router 之前,不然就無法解決跨域的問題哦!

好了重新啟動服務:node app.js

getSuccess.png

examples 這個字段就是我們剛剛在表里插入的數據,get 請求成功了,再來使用 post 請求向數據庫里插入一條數據吧:

我們還是在 App.vue 中寫:

mounted() {
  // this.$http.getExample({name: 'frank'});
  this.$http.postExample({msg: 'test post request!'});
}
postSuccess.png

post 請求也成功了,好了現在來看看數據庫的 example 表:

example.png

沒毛病,目前!我們已經成功實現前后端分離,并且已經打通了前后端的交互,后續(xù)的開發(fā)無非就是依葫蘆畫瓢拓展開發(fā)了。

產品詳述

文章開頭我做了一下產品簡介,正式開發(fā)之前就需要了解一下到底要做什么,做成什么樣!畫個流程圖,或者做個 PRD 什么的,當然這些對于我來說就算了,全靠 YY,我就大致說一下吧:

上面的預覽圖看得出來大致的結構,我們主要分為五個模塊(其實應該不算登錄就四個):

登錄

登錄模塊兼注冊,這里參考一下用戶表(后端項目 models/user.js 文件),用戶的密碼我單獨寫了一個 password 表,用戶id作為關聯,密碼是經過加密的,未使用明文。用戶注冊的時候不需要詳細信息,但是發(fā)布課程就需要用戶完善個人信息了(比如學??偟糜邪桑?,比如用戶可以收藏課程,那么增加一個 collections 字段(類型為數組)用來存放課程的 id

代課

代課模塊主要就是展示課程的信息,用戶點擊之后可以查看詳細的信息,當然部分信息是發(fā)布者希望讓你看到的才會展示,該模塊包括后續(xù)的發(fā)布模塊,課程模塊都依賴于 courses 表,這里也不多贅述了...

發(fā)布

發(fā)布課程需要用戶完善必要的信息才能發(fā)布,發(fā)布課程也需要一些課程相關的必要信息(比如沒有時間地點怎么上課?),當然為了一些其他的 PY 交易,發(fā)布者可以選擇提供一些額外的信息或者備注。

課程

課程模塊,分為三個 tab,分別為我發(fā)布的、我代課的和我收藏的,點擊之后會展示詳細信息

我的

我的模塊,主要就是個人信息的展示,支持個人信息的修改,對應的表也就是用戶表

之后就是正式開發(fā)了,我也就不重復的講代碼了,文章的開頭結尾我會放上 git 倉庫的地址,希望大家點個 start 這將是我這個單身狗最大的快樂(題外話扯多了)

基礎數據來源

最大的問題就是數據來源,用戶可以選擇自己的學校(大學),因此需要全國的大學信息作為數據基礎,全國高校名單 可以在這個網站查到,但是需要自己做一些調整,而且不夠完善,本來打算用爬蟲搞數據下來,但是我在網上看到某位大佬有全國高校的數據,所以...感謝大佬,讓我節(jié)約了很多時間?。▽嵲诓缓靡馑?,我目前不知道是在哪看到的了,不能貼出大佬的地址,再次表示感謝),好了,基礎數據有了剩下就是添磚加瓦,下面還是看一下學校的數據結構(單條數據):

{
  "_id" : ObjectId("5b7648965675dd2687a5b680"),
  "id" : "3500",
  "name" : "四川大學",
  "website" : "http://www.scu.edu.cn/",
  "provinceId" : 23,
  "level" : "本科",
  "abbreviation" : "scu",
  "city" : "成都市"
}

此外,還有一些全國城市的信息,省的信息等,這些信息我都和課程、用戶做了關聯方便后續(xù)開發(fā)的擴展,比如可以統計展示一些可視化圖表等。

總結

項目比較簡單,但是也算是一次比較全的實踐了,所用的框架技術(Vue、Koa、mongodb)等都是目前比較火的,對于初學者還是比較有意義的(本人也是第一次用,這篇文章算是學習筆記了)。

不足之處:在 代課 模塊應該區(qū)分一下用戶應該默認獲取同校的課程,當然可以加上查詢其它或所有學校的課程,獲取列表應該做分頁,前段最好還是下拉刷新...諸如此類的問題就不多說了!如果正在看文章的你有興趣的話可以在此基礎上優(yōu)化。

此外,后端的日志,參數等統一處理也沒有做...諸如此類的問題還有...

最后總結一下,學習才能進步...

希望你不吝賜教,可以的話給顆 star 鼓勵一下吧:

前端 gitHub 地址

后端 gitHub 地址

PS: 再次提醒,數據庫導出的 json 文件我放在了后端倉庫的 db-daike 目錄下

?著作權歸作者所有,轉載或內容合作請聯系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 178,765評論 25 709
  • 關于Mongodb的全面總結 MongoDB的內部構造《MongoDB The Definitive Guide》...
    中v中閱讀 32,275評論 2 89
  • 這一回頭,他反倒沒被安慰住,眼睛還瞪得老大,直直盯著我。 盯著我?認真一看,發(fā)現他是在直直盯著我身后…… 意識到身...
    楚兮君閱讀 319評論 0 0
  • 【敬畏】-【體驗】-【持續(xù)】-【交給】-【顯現】 1、缺啥補啥,怕啥練啥 2、一切為我所用,所用為團隊 3、我要變...
    京心達寧威閱讀 101評論 0 0
  • 最近在網上看到一個段子:兩天喜歡三天愛 ,七天追不到就拜拜 ,這種喜歡簡直比路邊的叫賣聲還要廉價。 現代的不僅生活...
    家家有999本難念的經閱讀 537評論 0 0

友情鏈接更多精彩內容