Vue全家桶+koa2+MySql(sequelize)重構“零食商販”項目

前言

原項目使用微信小程序配合ThinkPHP5.0打造的微信小程序商城,作爲一名“中端工程師”來講能夠使用javascript前後一起梭也是工作之餘非常愉快的事情。所以如果你也想梭一把,那麼可以繼續往下看。

github項目地址

項目不足

1.前端部分沒有100%還原設計稿,因爲壓根就沒有設計稿。唯一的標準就是自己的“像素眼”。
2.作爲WEB項目自然而然就閹割了微信的登錄與支付體系。當然登錄體系也許會加上。
3.後端部分沒有做嚴格的容錯處理,這裏更多的是提供一些問題的解決方法和分享自己遇到的坑兒。

項目運行效果

項目運行效果

前端部分

前端部分好像沒有什麼好說的了,網上vue全家桶項目一搜一大把。這裏主要分享一下從前端角度如何分解產品設計稿以及css模塊化的處理。本篇文章將重點放在服務端上。

分解一款產品

不知道其他小夥伴拿到設計稿是如何開始的,記得纔開始寫前端的時候也是根據設計稿從上到下,從左到右一步一步實現。不過往往這樣的開發流程遇到大的需求變更,或是產品同學提不切實際需求的時候是非常頭痛的。這裏提一句,前端小夥伴一定要多多參加產品需求會,一方面可以增加對公司業務的理解,另一方面可以把產品同學不切實際的需求扼殺在搖籃中,防止拍腦袋決策出現。

分解設計稿
“零食商販”項目雖然頁面有多個,但是我們分解設計稿就會發現其實該項目由這幾個部分組成。

Header與Footer
這一部分稱爲公共部分。基本上每個頁面都由header、footer以及中間主要內容組成。
圖片描述

layout
這一部分稱爲“基模塊”。也就是頁面大部分都是由該模塊組合而成,無非是一些內容的增減,實際開發中完全可以通過數據以及css達到各個頁面個性化定製需求。

圖片描述
圖片描述
圖片描述

以上三個頁面都一樣,只是換了馬甲。
結構抽象出來應該就是這樣:

//vue模板
<template>
  <div>
    <!-- 小標題 -->
    <p>{{ title }}</p>
    <slot></slot>
    <div>
      <!-- 產品模塊 -->
      <div>
          <img src="" alt="" >
          <p >{{ item.name }}</p>
          <p >{{ item.price }}</p>
      </div>
    </div>
  </div>
</template>

css模塊化

雖然vue提供了scope來方便編寫組件內部的css,防止css名相互污染。但有時候scope造成的作用域問題不方便調試。所以這裏採用了同樣流行的CSS Modules
開啓方式也很簡單,如果是使用vue-cli方式構建的vue項目,只需要兩步即可開啓:

進入文件夾build/vue-loader-conf.js

module.exports = {
  // css模塊化
  cssModules: {
    // 通過給類名加入唯一前綴防止類名衝突
    localIdentName: '[name]---[local]---[hash:base64:5]',
    camelCase: true
  }
}

在每個組件中申明css modules並使用

<template>
   //$style部分將會替換爲'[name]---[local]---[hash:base64:5]'
  <div :class="$style.header"></div>
</template>

<style lang='scss' module>
.header{
  color:blue
}
</style>

css modules 的核心原理就是通過加入唯一的class類名從而防止css類名衝突。本質上的效果與scope是一樣的。

一款“異常簡陋”的輪子

項目寫到一半,才發現需要一款符合微信官方風格的UI組件,雖然官方UI組件顏值上並不高。但是爲了視覺上的統一,又苦於網上沒有找到過於簡陋的UI組件,所以自己封裝了一個。目前只有picker組件和基於picker組件的地址選擇組件。
npm 命令直接安裝,本項目默認是安裝好了的。因爲簡陋所以也就不提供什麼文檔了...... 具體用法可以看項目內部實現。

npm install only-ui --save

後端部分

項目後端部分採用koa2搭配mysql數據實現,koa2官網是這樣介紹的:

Koa 是一個新的 web 框架,由 Express 幕後的原班人馬打造, 致力於成爲 web 應用和 API 開發領域中的一個更小、更富有表現力、更健壯的基石。 通過利用 async 函數,Koa 幫你丟棄回調函數,並有力地增強錯誤處理。 Koa 並沒有捆綁任何中間件, 而是提供了一套優雅的方法,幫助您快速而愉快地編寫服務端應用程序。

正是因爲koa2輕量,沒有一些官方性的約束,你可以很方便搭建自己的前端項目。但同時也帶來了一些問題,“一千個人就有一千種koa MVC的寫法”,所以如果是大一點項目或是團隊項目還是比較推薦egg.js來編寫。

項目結構

圖片描述

  1. server:根目錄
  2. api:存放的是我們項目的數據接口
  3. database:數據庫腳本文件,可以直接導入到navicate中使用
  4. dbs:數據庫配置以及數據模型(sequelize)
  5. public:一些資源圖片就存放在這裏
  6. view:視圖模板(本項目不需要)
  7. app.js:koa主文件
  8. index.js入口文件

初始化項目

//通過koa-generator快速搭建koa2服務
npm install -g koa-generator

//創建項目 輸入項目名稱
koa2 -e [項目名稱]

//安裝依賴
npm install

這樣我們基本上創建了一個比較簡單的koa2項目,我們來看一下現在已經安裝了哪些依賴

"dependencies": {
    "debug": "^2.6.3",
    "ejs": "~2.3.3", //ejs模板,因爲我們創建項目的時候用的ejs
    "koa": "^2.2.0",
    "koa-bodyparser": "^3.2.0", //解析request
    "koa-convert": "^1.2.0",
    "koa-json": "^2.0.2", //格式化json數據
    "koa-logger": "^2.0.1", //系統日誌
    "koa-onerror": "^1.2.1", //錯誤處理
    "koa-router": "^7.1.1", //路由
    "koa-static": "^3.0.0", //靜態資源處理
    "koa-views": "^5.2.1"//模板渲染
  },
  "devDependencies": {
    "nodemon": "^1.8.1" //可以隨時監聽服務端文件改動,並更新
  }

前面說了,koa就像一塊電腦主板一樣,需要什麼東西自己可以往上面加。這裏有更多中間件,如果還是沒有你需要的,你完全可以自己寫一個造福社區。
只有以上的中間件還是不夠的,比如我並不希望使用require語法導入模塊所以我們換成 es6 modules方式導入。這裏還需要安裝:

 "babel-core": "^6.26.3",
 "babel-preset-env": "^1.7.0",
 "babel-preset-es2015": "^6.24.1",
 "babel-register": "^6.26.0",

同時修改項目結構

//index.js
// 啓動文件
require('babel-register')
({
  'presets':['env']
})

require('./app.js')

這樣我們就可以在主文件中使用import導入我們需要的模塊。(中間件的導入在在項目中有清晰的註釋)
圖片描述

我們來嘗試啓動一下koa2服務,啓動之前要修改一下npm(你怕嗎) script

"scripts": {
    "start": "nodemon index.js", //使用nodemon 啓動index文件
},

數據庫

服務端開發怎麼能少了數據庫,不知道是不是錯覺,koa項目好像更多的是配合mongodb來使用。本項目使用mysql完全是因爲自己的習慣,再一個使用mongodb處理複雜一點的數據表間關係確實有點頭痛。。。
我們在這裏引入sequelize來操作mysql,畢竟使用原生sql顯得不是那麼優雅!什麼是sequelize?

Sequelize 是一個基於 promise 的 Node.js ORM, 目前支持 Postgres, MySQL, SQLite 和 Microsoft SQL Server. 它具有強大的事務支持, 關聯關係, 讀取和複製等功能.

通俗一點說,就好比Java中的hibernate,mongodb中的mongoose。讓我們以面向對象的方式操作數據庫。
現在讓我們來安裝sequelize。mysql的安裝

sequelize中文文檔

1. 安裝sequelize

// 安裝sequelize
$ npm install --save sequelize
// 安裝驅動
$ npm install --save mysql2

2. 配置sequelize

既然我們使用sequelize操作數據庫,那麼一番基本的配置一定是要有的。

//config.js
// sequelize配置文件
export default {
  // 數據庫名稱
  database: '',
  // 用戶名
  username: '',
  // 密碼
  password: '',
  // 地址
  host: '127.0.0.1',
  // 使用什麼數據庫
  dialect: 'mysql',
  // 連接池
  pool: {
    max: 5,
    min: 0,
    acquire: 30000,
    idle: 10000
  },
  // 數據表全局配置
  define:{
    //是否凍結表名,最好設置爲true,要不sequelize會自動給表名加上覆數s造成查詢數據失敗。
    //mongoose也有這樣的問題...
    freezeTableName:true,
    // 是否爲表添加 createdAt 和 updatedAt 字段
    // createdAt 記錄表的創建時間
    // updatedAt 記錄字段更新時間
    timestamps:false,
    // 是否爲表添加 deletedAt 字段
    // 在日常開發中刪除數據記錄是一大禁忌,因此我們刪除數據並不會真正刪除,而是爲他添加
    // deletedAt字段
    paranoid:false,
    //是否開啓op
    operatorsAliases: false
  },
  // 時區
  timezone: '+08:00'
}

如此一番操作,sequelize已經與mysql建立起聯繫,但還無法工作,我們需要給數據表建立模型。建立模型之前我們導入剛剛已經配置好的文件。如下圖
圖片描述
index.js中導入我們的配置config.js文件,其他的文件都是模型(可以把它理解爲數據庫中的表,讓我們更好操作它)。

3. 定義模型

我們在navicat中看到的表長這個樣子
圖片描述

我們的模型長這個樣子:

//banner.js
// banner 模型
export default (sequelize, DataTypes) => {
 //這裏的banner爲你的數據表名
  return sequelize.define('banner', {
    id: {
    //定義類型
      type: DataTypes.INTEGER(),
      //主鍵
      primaryKey: true
    },
    productsId: {
    //定義類型
      type: DataTypes.INTEGER(),
    },
    img_id: {
    //定義類型
      type: DataTypes.INTEGER(),
    }
  })
}

然後導入到index.js中統一管理(一定要讓你的所有模型在同一個sequelize實例下,曾經這個問題困擾了我很久。。。)

//index.js
import Sequelize from 'sequelize'
import config from '../config.js'

// 實例化sequelize
export const sequelize = new Sequelize(config)

// 導入模型統一管理(推薦使用官方方法)
export const Banner = sequelize.import(__dirname + '/banners.js')

4. 建立表與表之間關係

表與表之間無外乎:

一對一 belongsto
外鍵一對一 hasone
一對多 hasmany
多對多 belongsToMany

拿我們項目中的banner與image來舉例,banner指向唯一image,image對應唯一banner那麼他們之間關係就爲一對一。

//定義關係
Banner.belongsTo(Image, {
  foreignKey: 'img_id',
  targetKey: 'id'
})

現在我們建立了模型也定義了模型間關係,現在我們開始來使用。

5.爲前端提供接口

還是以banner爲例

//api/banner.js
// banner接口
import Router from 'koa-router'

// 引入用戶模型
import { Banner,Image } from '../dbs/models/index.js'
//定義接口前綴
let router = new Router({
  prefix:'/banner'
})

//暴露給前端的接口
router.get('/', async (ctx,next)=>{
  let banner = await Banner.findAll({
   //聲明要包含的模型,之前聲明的關係將在這裏發揮作用
    include:[{
      model:Image
    }],
    //過濾不需要的數據
    attributes:{
      exclude:['img_id']
    }
  })
//最終返回的數據
    ctx.body = {
      banner
    }
})

export default router

最後我們需要把路由導入到主文件中。

//app.js
//引入
import banner from './api/banner.js'
//使用
app.use(banner.routes()).use(banner.allowedMethods())

現在你可以在前端通過axios訪問你的數據接口了,我們看一下最終執行效果。
sequelize已經自動幫我們生成了sql語句:
圖片描述

postman中的數據:
圖片描述

其他更詳細的內容可以查看mini-shop

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章