第一篇、搭建前後端環境,開發一個簡單的登錄功能

一、環境準備

  • 作業系統:支援 macOS,Linux,Windows
  • 運行環境:建議選擇 LTS 版本,最低要求 8.x。

二、快速初始化服務端eggjs項目

Egg.js 為企業級框架和應用而生,我們希望由 Egg.js 孕育出更多上層框架,幫助開發團隊和開發人員降低開發和維護成本。

註:Egg.js 縮寫為 Egg

設計原則

我們深知企業級應用在追求規範和共建的同時,還需要考慮如何平衡不同團隊之間的差異,求同存異。所以我們沒有選擇社區常見框架的大集市模式(集成如資料庫、模板引擎、前端框架等功能),而是專註於提供 Web 開發的核心功能和一套靈活可擴展的插件機制。我們不會做出技術選型,因為固定的技術選型會使框架的擴展性變差,無法滿足各種訂製需求。通過 Egg,團隊的架構師和技術負責人可以非常容易地基於自身的技術架構在 Egg 基礎上擴展出適合自身業務場景的框架。

Egg 的插件機制有很高的可擴展性,一個插件只做一件事(比如 Nunjucks 模板封裝成了 egg-view-nunjucks、MySQL 資料庫封裝成了 egg-mysql)。Egg 通過框架聚合這些插件,並根據自己的業務場景訂製配置,這樣應用的開發成本就變得很低。

Egg 奉行『約定優於配置』,按照一套統一的約定進行應用開發,團隊內部採用這種方式可以減少開發人員的學習成本,開發人員不再是『釘子』,可以流動起來。沒有約定的團隊,溝通成本是非常高的,比如有人會按目錄分棧而其他人按目錄分功能,開發者認知不一致很容易犯錯。但約定不等於擴展性差,相反 Egg 有很高的擴展性,可以按照團隊的約定訂製框架。使用 Loader 可以讓框架根據不同環境定義默認配置,還可以覆蓋 Egg 的默認約定。

與社區框架的差異

Express 是 Node.js 社區廣泛使用的框架,簡單且擴展性強,非常適合做個人項目。但框架本身缺少約定,標準的 MVC 模型會有各種千奇百怪的寫法。Egg 按照約定進行開發,奉行『約定優於配置』,團隊協作成本低。

Sails 是和 Egg 一樣奉行『約定優於配置』的框架,擴展性也非常好。但是相比 Egg,Sails 支援 Blueprint REST API、WaterLine 這樣可擴展的 ORM、前端集成、WebSocket 等,但這些功能都是由 Sails 提供的。而 Egg 不直接提供功能,只是集成各種功能插件,比如實現 egg-blueprint,egg-waterline 等這樣的插件,再使用 sails-egg 框架整合這些插件就可以替代 Sails 了。

特性

安裝

推薦直接使用腳手架,只需幾條簡單指令,即可快速生成項目(npm >=6.1.0):

$ mkdir egg-example && cd egg-example
$ npm init egg --type=simple
$ npm i

得到目錄結構如下:

egg-project
├── package.json
├── app.js (可選)
├── agent.js (可選)
├── app
|   ├── router.js
│   ├── controller
│   |   └── home.js
│   ├── service (可選)
│   |   └── user.js
│   ├── middleware (可選)
│   |   └── response_time.js
│   ├── schedule (可選)
│   |   └── my_task.js
│   ├── public (可選)
│   |   └── reset.css
│   ├── view (可選)
│   |   └── home.tpl
│   └── extend (可選)
│       ├── helper.js (可選)
│       ├── request.js (可選)
│       ├── response.js (可選)
│       ├── context.js (可選)
│       ├── application.js (可選)
│       └── agent.js (可選)
├── config
|   ├── plugin.js
|   ├── config.default.js
│   ├── config.prod.js
|   ├── config.test.js (可選)
|   ├── config.local.js (可選)
|   └── config.unittest.js (可選)
└── test
    ├── middleware
    |   └── response_time.test.js
    └── controller
        └── home.test.js

啟動項目:

$ npm run dev
$ open //localhost:7001

三、建立一個會員資訊表,mysql>=5.7

建立一個會員資訊表,有用戶名密碼等資訊

四、使用egg-mysql插件訪問資料庫

egg框架提供了 egg-mysql 插件來訪問 MySQL 資料庫。這個插件既可以訪問普通的 MySQL 資料庫,也可以訪問基於 MySQL 協議的在線資料庫服務。

安裝與配置

安裝對應的插件 egg-mysql :

$ npm i --save egg-mysql

開啟插件:

// config/plugin.js
exports.mysql = {
  enable: true,
  package: 'egg-mysql',
};

在 config/config.${env}.js 配置各個環境的資料庫連接資訊。

單數據源

如果我們的應用只需要訪問一個 MySQL 資料庫實例,可以如下配置:

// config/config.${env}.js
exports.mysql = {
  // 單資料庫資訊配置
  client: {
    // host
    host: 'mysql.com',//本機127.0.0.1
    // 埠號
    port: '3306',
    // 用戶名
    user: 'test_user',
    // 密碼
    password: 'test_password',
    // 資料庫名
    database: 'users',//users
  },
  // 是否載入到 app 上,默認開啟
  app: true,
  // 是否載入到 agent 上,默認關閉
  agent: false,
};

使用方式:

await app.mysql.query(sql, values); // 單實例可以直接通過 app.mysql 訪問

多數據源

如果我們的應用需要訪問多個 MySQL 數據源,可以按照如下配置:

exports.mysql = {
  clients: {
    // clientId, 獲取client實例,需要通過 app.mysql.get('clientId') 獲取
    db1: {
      // host
      host: 'mysql.com',
      // 埠號
      port: '3306',
      // 用戶名
      user: 'test_user',
      // 密碼
      password: 'test_password',
      // 資料庫名
      database: 'test',
    },
    db2: {
      // host
      host: 'mysql2.com',
      // 埠號
      port: '3307',
      // 用戶名
      user: 'test_user',
      // 密碼
      password: 'test_password',
      // 資料庫名
      database: 'test',
    },
    // ...
  },
  // 所有資料庫配置的默認值
  default: {

  },

  // 是否載入到 app 上,默認開啟
  app: true,
  // 是否載入到 agent 上,默認關閉
  agent: false,
};

使用方式:

const client1 = app.mysql.get('db1');
await client1.query(sql, values);

const client2 = app.mysql.get('db2');
await client2.query(sql, values);

在資料庫里建一個users表

service層

  • 在app目錄下新建service目錄,用來放置service層的程式碼

  • 新建一個users.js文件

  'use strict';
  
  const Service = require('egg').Service;
  
  class UsersService extends Service {
  
      async account(name, pwd) {
          const result = await this.app.mysql.select('users', { // 搜索 users 表
              where: { name: name, password: pwd }, // WHERE 條件
          });
          return result;
      }
  }
  
  module.exports = UsersService;

controller層

  • 在controller目錄下新建account.js
'use strict';

const Controller = require('../core/base_controller');

class AccontController extends Controller {
  async account() {
    const { ctx } = this;
    //解析post請求的參數
    const name = ctx.request.body.loginname;
    const pwd = ctx.request.body.password;
    let res = await ctx.service.users.account(name,pwd);//調用service層的方法
    this.success(res);
    
  }
}

module.exports = AccontController;
  • 這裡base_controller是自己創建的一個基類controller,在app目錄下新建「/core/base_controller.js」。
const { Controller } = require('egg');
class BaseController extends Controller {
  success(data) {
    this.ctx.body = {
      success: true,
      data,
    };
  }

  notFound(msg) {
    msg = msg || 'not found';
    this.ctx.throw(404, msg);
  }
}
module.exports = BaseController;
  • 配置服務端路由
'use strict';

/**
 * @param {Egg.Application} app - egg application
 */
module.exports = app => {
  const { router, controller } = app;
  router.get('/', controller.home.index);
  router.post('/account', controller.accont.account);//我們的簡單登錄驗證接受post請求
    // controller.accont.account 是accountController里的account方法,,,accont拼寫錯了
};

這樣我們簡單的一個服務端就寫好了。

五、初始化一個前端vue項目

  • 安裝一下vue的clivue-cli,初始化一個vue工程
npm install -g @vue/cli-service-global
vue ui //用圖形化介面創建vue工程

六、引用element-ui 插件

自己配一個喜歡的主題色,我自己瞎配了個綠色。

import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import './plugins/element.js'

Vue.config.productionTip = false

new Vue({
  router,
  store,
  render: h => h(App)
}).$mount('#app')

七、封裝請求模組

安裝axios

在src目錄下新建「http/http.js」,在這裡我們先初步簡單封裝一下我們前端的請求模組:

import axios from "axios";

axios.defaults.timeout = 5000;
axios.defaults.baseURL = '//127.0.0.1:7001';
// axios.defaults.headers.common['Authorization'] = AUTH_TOKEN;
axios.defaults.headers.post['Content-Type'] = 'application/json';



function get(url, data = {}) {
    return new Promise((resolve, reject) => {
        axios.get(url, {
            params: data
        }).then(response => {
            resolve(response.data);
        }).catch(err => {
            reject(err)
        })
    })
}

function post(url, data = {}) {
    return new Promise((resolve, reject) => {
        axios.post(url, data).then(response => {
            resolve(response.data);
        }).catch(err => {
            reject(err)
        });
    })
}

function patch(url, data = {}) {
    return new Promise((resolve, reject) => {
        axios.patch(url, data)
            .then(response => {
                resolve(response.data);
            }, err => {
                reject(err)
            })
    })
}

function put(url, data = {}) {
    return new Promise((resolve, reject) => {
        axios.put(url, data)
            .then(response => {
                resolve(response.data);
            }, err => {
                reject(err)
            })
    })
}

export default { get,post,patch,put }

在新建一個api.js文件用來管理一下我們前端的請求介面:

import http from "./http";

/**
 * login 頁面
 */
export const login = (data) => http.post("/account",data);//用戶登錄

八、創建登錄頁面,實現簡單登錄

在views里新建「login/login.vue」

<template>
    <div>
        <div style="height: 300px;margin-top: 100px">
            <p style="text-align: center;"><img src="../../assets/logo.png"></p>
        </div>
        <div style="width: 350px;margin: 0 auto">

            <el-form ref="form" :model="form">
                <el-form-item label="">
                    <el-input v-model.trim="form.loginname" placeholder="用戶名"></el-input>
                </el-form-item>
                <el-form-item label="">
                    <el-input v-model.trim="form.password" placeholder="密碼"></el-input>
                </el-form-item>
                <el-form-item label="">
                    <el-button type="primary" style="width: 100%" @click="login">登錄</el-button>
                </el-form-item>
            </el-form>
        </div>
    </div>
</template>

<script>
    import {login} from "../../http/api";

    export default {
        name: "loign",
        data() {
            return {
                form: {
                    loginname: "",
                    password: ""
                }
            }
        },
        methods: {
            login() {
                login(this.form).then(res => {
                    if (res.success) {
                        if(res.data.length > 0){
                            this.$message.success("成功!頁面跳轉中");
                            this.$router.push("/conent");
                        }else {
                            this.$message.error("失敗");
                        }

                    }else {
                        this.$message.error("失敗")
                    }
                }).catch(e => {
                    console.log(e)
                })
            }
        }
    }
</script>

<style scoped>

</style>

配置一下前端路由

    {
        path: '/',
        name: 'Login',
        component: Login
    },
      npm run serve

好啦,一個簡單的登錄完成了!
快來打賞我吧!