为了账号安全,请及时绑定邮箱和手机立即绑定

Vue全家桶+koa2+MySql(sequelize)重构“零食商贩”项目

前言

原项目使用微信小程序配合ThinkPHP5.0打造的微信小程序商城,作为一名“中端工程师”来讲能够使用javascript前后一起梭也是工作之余非常愉快的事情。所以如果你也想梭一把,那么可以继续往下看。

项目不足

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的安装

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

点击查看更多内容
1人点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
Web前端工程师
手记
粉丝
7
获赞与收藏
8

关注作者,订阅最新文章

阅读免费教程

感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消