首頁>技術>

寫在前面

閒來無事,試了一下 Koa,第一次搞感覺還不錯,這個專案比較基礎但還是比較完整了,還是有一定的參考價值

專案簡介

以下是專案地址,希望給個 star,鼓勵一下:

前端 gitHub 地址 : https://github.com/frankxjkuang/daike-client

後端 gitHub 地址: https://github.com/frankxjkuang/daike-server

PS: 資料庫我放在了後端專案的 db-daike 目錄下

專案名:《代課》

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

效果預覽:

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

技術棧

主要還是分為三大塊:前端(Vue) + 後端(NodeJS - Koa)+ 資料庫(MongoDB)

前端

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

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

css採用 scss

登入頁的logo也是用的一個網站線上製作的,不好意思,網址我給忘了

專案結構(大致)如下:

├── axios // 對 axios進行 二次封裝│ └── interface // api 檔案目錄├── src│ ├── router // 路由配置 │ └── views // 路由頁面└── vuex // 全域性的狀態 └── views // 按路由模組進行狀態分組複製程式碼

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" }}// ...複製程式碼

後端的專案結構有必要說一些,這個是我參考一些比較規範的專案自己搞的,也是比較隨意了,哈哈(我也是第一次這麼搞):

├── app // 對 axios進行 二次封裝│ └── controllers // 控制器檔案目錄,用來操作資料庫│ │ └── ... // 對應操作的表,這裡就省略了│ ├── middleware// 自定義中介軟體目錄│ ├── models // 定義的表結構│ │ └── ... // 對應的表,這裡就省略了│ └── utils // 工具模組目錄│ │ └── ... // 工具模組,這裡就省略了├── rotes // 路由檔案│ ├── router // 路由配置 │ └── views // 路由頁面└── vuex // 全域性的狀態 └── views // 按路由模組進行狀態分組├── app.js // 專案入口檔案└── config.js // 配置檔案複製程式碼
資料庫

依稀還得大學的時候學過 SQL,不行了畢業太久忘了,所以這裡使用 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 檔案目錄下載

工具和專案結構咱們都搞完了,就開始寫程式碼吧

打通前後端

咱們這個專案採用前後端分離的方式進行開發,為了開發的順暢進行,我們先除錯一下:前端發個請求,後端接收訊息,並從資料庫中拿到資料響應給前端(我們以post和get方法為例寫兩個介面),get請求獲取資料,post請求插入一條資料;

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

連線資料庫

啟動一個 Node 服務連線資料庫,後續的操作都是基於資料庫的:

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 之後看到如圖所示的列印就說明資料庫連線成功了:

新建一張表,就叫 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' 資料:

編寫對應 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}複製程式碼編寫對應的路由模組

在 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;複製程式碼作為中介軟體在入口檔案中使用

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

在入口檔案 app.js 中增加兩句話:

const example_router = require('./routes/api/example_router');app.use(example_router.routes()).use(example_router.allowedMethods());複製程式碼

重新啟動服務 node app.js

前端請求介面

這裡就不多說了,有興趣的可以直接看倉庫的程式碼就好了,大致是兩個介面有興趣的可以看看我前面寫的文章二次封裝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

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

後端解決跨域

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

const cors = require('koa2-cors');app.use(cors());複製程式碼

PS: 這裡要注意一下,js 是單執行緒語言,中介軟體是有執行先後順序的,所以 app.use(cors());的使用必須在 router 之前,不然就無法解決跨域的問題哦!

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

examples 這個欄位就是我們剛剛在表裡插入的資料,get 請求成功了,再來使用 post 請求向資料庫裡插入一條資料吧:

我們還是在 App.vue 中寫:

mounted() { // this.$http://127.0.0.1/vhost/conf/img_echo.php?w=640&h=334&src=http.getExample({name: 'frank'}); this.$http://127.0.0.1/vhost/conf/img_echo.php?w=640&h=334&src=http.postExample({msg: 'test post request!'});}複製程式碼

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

沒毛病,目前!我們已經成功實現前後端分離,並且已經打通了前後端的互動,後續的開發無非就是依葫蘆畫瓢拓展開發了。

產品詳述

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

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

登入

登入模組兼註冊,這裡參考一下使用者表(後端專案 models/user.js 檔案),使用者的密碼我單獨寫了一個 password 表,使用者id作為關聯,密碼是經過加密的,未使用明文。使用者註冊的時候不需要詳細資訊,但是釋出課程就需要使用者完善個人資訊了(比如學校總得有吧),比如使用者可以收藏課程,那麼增加一個 collections 欄位(型別為陣列)用來存放課程的 id

代課

代課模組主要就是展示課程的資訊,使用者點選之後可以檢視詳細的資訊,當然部分資訊是釋出者希望讓你看到的才會展示,該模組包括後續的釋出模組,課程模組都依賴於 courses 表,這裡也不多贅述了...

釋出

釋出課程需要使用者完善必要的資訊才能釋出,釋出課程也需要一些課程相關的必要資訊(比如沒有時間地點怎麼上課?),當然為了一些其他的 PY 交易,釋出者可以選擇提供一些額外的資訊或者備註。

我的

我的模組,主要就是個人資訊的展示,支援個人資訊的修改,對應的表也就是使用者表

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

基礎資料來源

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

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

此外,還有一些全國城市的資訊,省的資訊等,這些資訊我都和課程、使用者做了關聯方便後續開發的擴充套件,比如可以統計展示一些視覺化圖表等。

總結

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

不足之處:在 代課 模組應該區分一下使用者應該預設獲取同校的課程,當然可以加上查詢其它或所有學校的課程,獲取列表應該做分頁,前段最好還是下拉重新整理...諸如此類的問題就不多說了!如果正在看文章的你有興趣的話可以在此基礎上優化。

此外,後端的日誌,引數等統一處理也沒有做...諸如此類的問題還有...

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

希望你不吝賜教,可以的話給個贊+關注 鼓勵一下吧

  • BSA-TRITC(10mg/ml) TRITC-BSA 牛血清白蛋白改性標記羅丹明
  • 基於SpringBoot 2.0開發的美柚大資料研發任務排程平臺,原始碼分享