後端源碼:github.com/dumplingbao…前端
前端源碼:github.com/dumplingbao…vue
iview:一套基於 Vue.js 的高質量UI 組件庫,主流vue前端框架,比較適合先後端分離框架的搭建,固然你也能夠選擇其餘的node
koa2:基於nodejs平臺的下一代web開發框架,這裏咱們不選早期的目前用的最多的Express,也不選阿里開源的框架egg,咱們選擇則目前比較新的koa2,寫起來簡單,也易於學習mysql
sequelize:這個是個nodejs的ORM框架,用的比較多,關於這個框架的介紹,能夠看一下個人另外一篇博客,node之ORM框架。ios
搭建這個先後端分離的框架純屬娛樂加學習,寫此博客就是把搭建過程介紹一下,也做爲本身的一點心得吧。git
先找個輪子,這裏用狼叔的koa-generator來生成項目架構github
npm install koa-generator -g
koa2 dissplat //項目名稱
複製代碼
生成文檔結構web
.
├── bin
├── public
├── routes
├── view
├── package.json
└── app.js
複製代碼
既然是輪子,直接就能夠運行了sql
npm install
npm run dev
複製代碼
系統自動建立users表數據庫
(node:15140) [SEQUELIZE0002] DeprecationWarning: The logging-option should be either a function or false. Default: console.log
Executing (default): CREATE TABLE IF NOT EXISTS `users` (`id` INTEGER auto_increment , `nickname` VARCHAR(255), `email` VARCHAR(128) UNIQUE, `password` VARCHAR(255), `created_at` DATETIME, `updated_at` DATETIME NOT NULL, `deleted_at` DATETIME, PRIMARY KEY (`id`)) ENGINE=InnoDB;
Executing (default): SHOW INDEX FROM `users`
複製代碼
咱們先按這個結構走,由於咱們搭建先後端分離的框架,因此,public下面的圖片、樣式文件夾用不到,咱們就把sequelize的model、dao、service、配置文件等放到public下面,刪除public下面以前已有的文件
public下面新建一個config文件夾,裏面建立config.js,放數據庫的配置信息
module.exports = {
database: {
dbName: 'boblog',
host: 'localhost',
port: 3306,
user: 'root',
password: 'root'
}
}
複製代碼
新建一個utils文件夾,建立一個db.js,咱們簡單封裝,建立一個鏈接數據庫的工具類
const Sequelize = require('sequelize')
const {
dbName,
host,
port,
user,
password
} = require('../config/config').database
const sequelize = new Sequelize(dbName, user, password, {
dialect: 'mysql',
host,
port,
logging: true,
timezone: '+08:00',
define: {
// create_time && update_time
timestamps: true,
// delete_time
paranoid: true,
createdAt: 'created_at',
updatedAt: 'updated_at',
deletedAt: 'deleted_at',
// 把駝峯命名轉換爲下劃線
underscored: true,
scopes: {
bh: {
attributes: {
exclude: ['password', 'updated_at', 'deleted_at', 'created_at']
}
},
iv: {
attributes: {
exclude: ['content', 'password', 'updated_at', 'deleted_at']
}
}
}
}
})
// 建立模型
sequelize.sync({
force: false
})
module.exports = {
sequelize
}
複製代碼
接下來新建一個model文件夾,建立一個user.js,建立一個user的model
const moment = require('moment');
const bcrypt = require('bcryptjs')
const {Sequelize, Model} = require('sequelize')
const {db} = require('../utils/db')
class User extends Model {}
User.init({
// attributes
id: {
type: Sequelize.INTEGER,
primaryKey: true,
autoIncrement: true
},
// 暱稱
nickname: Sequelize.STRING,
// 郵箱
email: {
type: Sequelize.STRING(128),
unique: true
},
// 密碼
password: {
type: Sequelize.STRING,
set(val) {
// 加密
const salt = bcrypt.genSaltSync(10);
// 生成加密密碼
const psw = bcrypt.hashSync(val, salt);
this.setDataValue("password", psw);
}
},
created_at: {
type: Sequelize.DATE,
get() {
return moment(this.getDataValue('created_at')).format('YYYY-MM-DD');
}
}
}, {
db,
modelName: 'users'
// options
});
複製代碼
接下來新建一個dao文件夾,建立一個user.js,建立一個user的dao,負責CRUD
const {User} = require('../model/user')
const bcrypt = require('bcryptjs')
class UserDao {
// 建立用戶
static async createUser(v) {
const hasUser = await User.findOne({
where: {
email: v.email,
deleted_at: null
}
});
if (hasUser) {
throw new global.errs.Existing('用戶已存在');
}
const user = new User();
user.email = v.email;
user.password = v.password;
user.nickname = v.nickname;
return user.save();
}
// 驗證密碼
static async verifyEmailPassword(email, plainPassword) {
// 查詢用戶是否存在
const user = await User.findOne({
where: {
email
}
})
if (!user) {
throw new global.errs.AuthFailed('帳號不存在')
}
// 驗證密碼是否正確
const correct = bcrypt.compareSync(plainPassword, user.password);
if (!correct) {
throw new global.errs.AuthFailed('密碼不正確')
}
return user
}
// 刪除用戶
static async destroyUser(id) {
const user = await User.findOne({
where: {
id,
deleted_at: null
}
});
if (!user) {
throw new global.errs.NotFound('沒有找到此用戶');
}
user.destroy()
}
// 獲取用戶詳情
static async getUserInfo(id) {
const user = await User.findOne({
where: {
id
}
});
if (!user) {
throw new global.errs.NotFound('沒有找到用戶信息');
}
return user
}
// 更新用戶
static async updateUser(id, v) {
const user = await User.findByPk(id);
if (!user) {
throw new global.errs.NotFound('沒有找到用戶信息');
}
user.email = v.get('query.email');
user.password = v.get('query.password2');
user.nickname = v.get('query.nickname');
user.save();
}
static async getUserList(page = 1) {
const pageSize = 10;
const user = await User.findAndCountAll({
limit: pageSize,//每頁10條
offset: (page - 1) * pageSize,
where: {
deleted_at: null
},
order: [
['created_at', 'DESC']
]
})
return {
data: user.rows,
meta: {
current_page: parseInt(page),
per_page: 10,
count: user.count,
total: user.count,
total_pages: Math.ceil(user.count / 10),
}
};
}
}
module.exports = {
UserDao
}
複製代碼
直接下載iview-admin項目DEMO
# clone the project
git clone https://github.com/iview/iview-admin.git
// install dependencies
npm install
// develop
npm run dev
複製代碼
.
├── config 開發相關配置
├── public 打包所需靜態資源
└── src
├── api AJAX請求
└── assets 項目靜態資源
├── icons 自定義圖標資源
└── images 圖片資源
├── components 業務組件
├── config 項目運行配置
├── directive 自定義指令
├── libs 封裝工具函數
├── locale 多語言文件
├── mock mock模擬數據
├── router 路由配置
├── store Vuex配置
├── view 頁面文件
└── tests 測試相關
複製代碼
效果圖
默認菜單讀取routers.js,能夠根據權限組控制,也能夠根據權限讀取菜單進行加載,菜單裏面meta的配置說明以下,由於有些是路由,不顯示在菜單裏面,好比表單的CRUD操做。
/**
* iview-admin中meta除了原生參數外可配置的參數:
* meta: {
* title: { String|Number|Function }
* 顯示在側邊欄、麪包屑和標籤欄的文字
* 使用'{{ 多語言字段 }}'形式結合多語言使用,例子看多語言的路由配置;
* 能夠傳入一個回調函數,參數是當前路由對象,例子看動態路由和帶參路由
* hideInBread: (false) 設爲true後此級路由將不會出如今麪包屑中,示例看QQ羣路由配置
* hideInMenu: (false) 設爲true後在左側菜單不會顯示該頁面選項
* notCache: (false) 設爲true後頁面在切換標籤後不會緩存,若是須要緩存,無需設置這個字段,並且須要設置頁面組件name屬性和路由配置的name一致
* access: (null) 可訪問該頁面的權限數組,當前路由設置的權限會影響子路由
* icon: (-) 該頁面在左側菜單、麪包屑和標籤導航處顯示的圖標,若是是自定義圖標,須要在圖標名稱前加下劃線'_'
* beforeCloseName: (-) 設置該字段,則在關閉當前tab頁時會去'@/router/before-close.js'裏尋找該字段名對應的方法,做爲關閉前的鉤子函數
* }
*/
複製代碼
將main.js裏面的mock註釋掉,mock攔截並模擬後臺數據
// 實際打包時應該不引入mock
/* eslint-disable */
// if (process.env.NODE_ENV !== 'production') require('@/mock')
複製代碼
config.js配置baseUrl
/**
\* @description api請求基礎路徑
*/
baseUrl: {
dev: 'http://localhost:8888/',
pro: 'http://localhost:8888/'
},
複製代碼
後端建立util.js 建立token
const jwt = require('jsonwebtoken')
const {security} = require('../config/config')
// 頒佈令牌
const generateToken = function (uid, scope) {
const secretKey = security.secretKey;
const expiresIn = security.expiresIn;
const token = jwt.sign({
uid,
scope
}, secretKey, {
expiresIn: expiresIn
})
return token
}
module.exports = {
generateToken,
}
複製代碼
前端請求過濾,請求加token驗證,axios.js修改
if (!config.url.includes('/login')) {
// const base64 = Base64.encode(token + ':');
config.headers['Authorization'] = 'Basic ' + Base64.encode(Cookies.get(TOKEN_KEY) + ':')
}
複製代碼
後端採用basic-auth登陸認證,見auth.js
iview前端axios配置,找到axios.js配置文件
getInsideConfig () {
const config = {
baseURL: this.baseUrl,
changeOrigin: true,
headers: {
'Content-Type': 'application/json; charset=utf-8',
'Access-Control-Allow-Origin': '*'
}
}
return config
}
複製代碼
後端設置CORS來解決跨域問題,配置app.js,須要安裝npm對應的包
const cors = require('@koa/cors');
app.use(cors({
origin: function (ctx) {
// if (ctx.url === '/api') {
// return "*"; // 容許來自全部域名請求
// }
// return 'http://localhost:8080';
return "*"; // 容許來自全部域名請求
},
exposeHeaders: ['WWW-Authenticate', 'Server-Authorization'],
maxAge: 5,
credentials: true,
allowMethods: ['OPTIONS','GET', 'PUT','POST', 'DELETE'], //設置容許的HTTP請求類型
allowHeaders: ['Origin', 'Content-Type', 'Accept', 'Access-Control-Allow-Origin', 'Authorization', 'X-Requested-With'],
}));
複製代碼
後端-util文件下
auth.js:訪問認證
error.js:異常錯誤封裝
help.js:請求封裝
util.js:jwt獲取token