知識點:
了解多租戶的資料庫設計方案
熟練使用PowerDesigner建構資料庫模型
了解前端工程的基本架構和執行流程
完成前端工程企業子產品開發
1 多租戶SaaS平台的資料庫方案
1.1 多租戶是什麼
多租戶技術(Multi-TenancyTechnology)又稱多重租賃技術:是一種軟體架構技術,是實作如何在多使用者環境下(此處的多使用者一般是面向企業使用者)共用相同的系統或程式元件,并且可確定各使用者間資料的隔離性。簡單講:在一台伺服器上運作單個應用執行個體,它為多個租戶(客戶)提供服務。從定義中我們可以了解:多租戶是一種架構,目的是為了讓多使用者環境下使用同一套程式,且保證使用者間資料隔離。那麼重點就很淺顯易懂了,多租戶的重點就是同一套程式下實作多使用者資料的隔離。
1.2 需求分析
傳統軟體模式,指将軟體産品進行買賣,是一種單純的買賣關系,客戶通過買斷的方式擷取軟體的使用權,軟體的源碼屬于客戶所有,是以傳統軟體是部署到企業内部,不同的企業各自部署一套自己的軟體系統
Saas模式,指服務提供商提供的一種軟體服務,應用統一部署到服務提供商的伺服器上,客戶可以根據自己的實際需求按需付費。使用者購買基于WEB的軟體,而不是将軟體安裝在自己的電腦上,使用者也無需對軟體進行定期的維護與管理
在SaaS平台裡需要使用共用的資料中心以單一系統架構與服務提供多數用戶端相同甚至可定制化的服務,并且仍可以保障客戶的資料正常使用。由此帶來了新的挑戰,就是如何對應用資料進行設計,以支援多租戶,而這種設計的思路,是要在資料的共享、安全隔離和性能間取得平衡。
1.3 多租戶的資料庫方案分析
目前基于多租戶的資料庫設計方案通常有如下三種:
獨立資料庫
共享資料庫、獨立 Schema
共享資料庫、共享資料表
1.3.1 獨立資料庫
獨立資料庫:每個租戶一個資料庫。
優點:為不同的租戶提供獨立的資料庫,有助于簡化資料模型的擴充設計,滿足不同租戶的獨特需求;如果出現故障,恢複資料比較簡單。
缺點: 增多了資料庫的安裝數量,随之帶來維護成本和購置成本的增加
這種方案與傳統的一個客戶、一套資料、一套部署類似,差别隻在于軟體統一部署在營運商那裡。由此可見此方案使用者資料隔離級别最高,安全性最好,但是成本較高
1.3.2 共享資料庫、獨立 Schema
(1) 什麼是Schema
oracle資料庫:在oracle中一個資料庫可以具有多個使用者,那麼一個使用者一般對應一個Schema,表都是建立在Schema中的,(可以簡單的了解:在oracle中一個使用者一套資料庫表)
mysql資料庫:mysql資料中的schema比較特殊,并不是資料庫的下一級,而是等同于資料庫。比如執行create schema test 和執行create database test效果是一模一樣的。
共享資料庫、獨立 Schema:即多個或所有的租戶使用同一個資料庫服務(如常見的ORACLE或MYSQL資料庫),但是每個租戶一個Schema。
優點: 為安全性要求較高的租戶提供了一定程度的邏輯資料隔離,并不是完全隔離;每個資料庫可支援更多的租戶數量。
缺點: 如果出現故障,資料恢複比較困難,因為恢複資料庫将牽涉到其他租戶的資料; 如果需要跨租戶統計資料,存在一定困難。
這種方案是方案一的變種。隻需要安裝一份資料庫服務,通過不同的Schema對不同租戶的資料進行隔離。由于資料庫服務是共享的,是以成本相對低廉。
1.3.3 共享資料庫、共享資料表
共享資料庫、共享資料表:即租戶共享同一個Database,同一套資料庫表(所有租戶的資料都存放在一個資料庫的同一套表中)。在表中增加租戶ID等租戶标志字段,表明該記錄是屬于哪個租戶的。
優點:所有租戶使用同一套資料庫,是以成本低廉。
缺點:隔離級别最低,安全性最低,需要在設計開發時加大對安全的開發量,資料備份和恢複最困難。
這種方案和基于傳統應用的資料庫設計并沒有任何差別,但是由于所有租戶使用相同的資料庫表,是以需要做好對
每個租戶資料的隔離安全性處理,這就增加了系統設計和資料管理方面的複雜程度。
1.4 SAAS-HRM資料庫設計
在SAAS-HRM平台中,分為了試用版和正式版。處于教學的目的,試用版采用共享資料庫、共享資料表的方式設計。正式版采用基于mysql的共享資料庫、獨立 Schema設計(後續課程)。
2 資料庫設計與模組化
2.1 資料庫設計的三範式
1.第一範式(1NF):確定每一列的原子性(做到每列不可拆分)
例如:假設我們的表中有一列是位址,裡面存的值是諸如:中國北京。那麼這樣就違反了第一範式,因為中國北京其實可以很好的拆分為中國和北京兩個,然後資料庫裡面可以出現兩列:國籍和城市。這樣才是符合第一範式的。
2.第二範式(2NF):在第一範式的基礎上,非主字段必須依賴于主字段(一個表隻做一件事)
假如:我們有一個學生表,裡面存的是使用者名,密碼等,如果再加上 英語成績,數學成績等字段,那麼就違反了第二範式。因為這樣顯得學生表不倫不類,不知道到底要存什麼樣的資料,是以為了滿足第二範式,就應該再建立一張成績表。
3.第三範式(3NF):在第二範式的基礎上,消除傳遞依賴。
例如:建立一個訂單表,有訂單單價,訂單個數,訂單總計三個字段,那麼這就違反了第三範式,因為總計這列的值完全可以通過單價乘以個數得到,不需要額外去存儲。還有一個例子,我們有一個員工表,裡面存了員工資訊,還有和公司關聯的company_id和company_name字段,同樣也違反了第三範式,因為我們隻要存了company_id,就可以查詢企業表,進而得到company_name。
以上說的三範式,出現的年代比較久遠了,那個時候伺服器的存儲的成本還比較高,也就是硬碟還比較貴,是以為了節省硬碟,就應該盡量減少硬碟的使用空間。而現在硬碟已經不是昂貴的東西了,是以就出現了反三範式:
反三範式:
反三範式是基于第三範式所調整的,沒有備援的資料庫未必是最好的資料庫,有時為了提高運作效率,就必須降低範式标準,适當保留備援資料。
就拿上面的第二範式的例子來說,如果我們遵守了第二範式,沒有存儲總計的值,那麼如果我們要做統計的時候,每次都要去計算單價乘以個數來得到總計,如果表中有十萬資料,就需要計算十萬次,這樣勢必會降低效率。而反三範式就是通過備援字段,來提高效率,隻需要通過查詢就可以得到結果,無需再次去邏輯運算,這也就是達到了以空間換時間的目的。
2.2 資料庫模組化
了解了資料的設計思想,那對于資料庫表的表設計應該怎麼做呢?答案是資料庫模組化
資料庫模組化:在設計資料庫時,對現實世界進行分析、抽象、并從中找出内在聯系,進而确定資料庫的結構。它主要包括兩部分内容:确定最基本的資料結構;對限制模組化。
2.2.1 模組化工具
對于資料模型的模組化,最有名的要數PowerDesigner,PowerDesigner是在中國軟體公司中非常有名的,其易用性、功能、對流行技術架構的支援、以及它的模型庫的管理理念,都深受設計師們喜歡。他的優勢在于:不用使用create table等語句建立表結構,資料庫設計人員隻關注如何進行資料模組化即可,将來的資料庫語句,可以自動生成。
2.2.2 使用pd模組化
1. 選擇建立資料庫模型 打開PowerDesigner,檔案->建立新模型->model types(選擇類型)->Physical DataModel(實體模型)
2. 控制台
3. 建立資料庫表
點即面闆按鈕中的建立資料庫按鈕建立資料庫模型
切換columns标簽,可以對表中的所有字段進行配置
如果基于傳統的資料庫設計中存在外鍵則可以使用面版中的Reference配置多個表之間的關聯關系,效果如下圖
4、導出sql語句
我們之前做的這些操作,都可以進行sql的導出,然後在資料庫中執行即可:
菜單欄:Databse——》Genarate Database:
生成的sql檔案内容如下:
/*==============================================================*/
/* DBMS name: MySQL 5.0 */
/* Created on: 2019/8/11 14:13:28 */
/*==============================================================*/
drop table if exists co_company;
drop table if exists co_dept;
/*==============================================================*/
/* Table: co_company */
/*==============================================================*/
create table co_company
(
id varchar(40) not null,
name varchar(200),
company_area varchar(200),
primary key (id)
);
/*==============================================================*/
/* Table: co_dept */
/*==============================================================*/
create table co_dept
(
id varchar(40) not null,
name varchar(400),
company_id varchar(40),
primary key (id)
);
alter table co_dept add constraint FK_Reference_1 foreign key (company_id)
references co_company (id) on delete restrict on update restrict;
3 前端架構
3.1 腳手架工程
此項目采用目前比較流行的前後端分離的方式進行開發。前端是在傳智播客研究院開源的前端架構(黑馬Admin商用背景模闆)的基礎上進行的開發。
技術棧
vue 2.5++
elementUI 2.2.2
vuex
axios
vue-router
vue-i18n
前端環境
node 8.++
npm 5.++
3.2 啟動與安裝
官網上提供了非常基礎的腳手架,如果我們使用官網的腳手架需要自己寫很多代碼比如登陸界面、主界面菜單樣式等内容。 課程已經提供了功能完整的腳手架,我們可以拿過來在此基礎上開發,這樣可以極大節省我們開發的時間。
(1)解壓提供的資源包
(2)在指令提示符進入該目錄,輸入指令:cnpm install
通過淘寶鏡像下載下傳安裝所有的依賴,幾分鐘後下載下傳完成
如果沒有安裝淘寶鏡像,請使用npm install
(3)關閉文法檢查
打開config/index.js 将useEslint的值改為false。
useEslint: false,
此配置作用: 是否開啟文法檢查,文法檢查是通過ESLint 來實作的。我們現在科普一下,什麼是ESLint : ESLint是一個文法規則和代碼風格的檢查工具,可以用來保證寫出文法正确、風格統一的代碼。如果我們開啟了Eslint , 也就意味着要接受它非常苛刻的文法檢查,包括空格不能少些或多些,必須單引不能雙引,語句後不可以寫分号等等,這些規則其實是可以設定的。我們作為前端的初學者,最好先關閉這種校驗,否則會浪費很多精力在文法的規範性上。如果以後做真正的企業級開發,建議開啟
(4)輸入指令:運作前端工程。
npm run dev
通路位址的端口号的修改:在src/config/index.js中可以找到8080端口,我可以改成8888,是以通路位址就是:localhost:8888
3.3 工程結構
整個前端工程的工程目錄結構如下:
3.4 執行流程分析
3.4.1 路由和菜單
路由和菜單是組織起一個背景應用的關鍵骨架。本項目側邊欄和路由是綁定在一起的,是以你隻有在@/router/index.js 下面配置對應的路由,側邊欄就能動态的生成了。大大減輕了手動編輯側邊欄的工作量。當然這樣就需要在配置路由的時候遵循很多的約定,這裡的路由分為兩種, constantRouterMap 和 asyncRouterMap 。
constantRouterMap 代通用頁面。
asyncRouterMap 代表那些業務中通過 addRouters 動态添加的頁面。
3.4.2 前端資料互動
一個完整的前端 UI 互動到服務端處理流程是這樣的:
1. UI 元件互動操作;
2. 調用統一管理的 api service 請求函數;
3. 使用封裝的 request.js 發送請求;
4. 擷取服務端傳回;
5. 更新 data;
從上面的流程可以看出,為了友善管理維護,統一的請求處理都放在 src/api 檔案夾中,并且一般按照 model緯度進行拆分檔案
api/
frame.js
menus.js
users.js
permissions.js
...
其中, src/utils/request.js 是基于 axios 的封裝,便于統一處理 POST,GET 等請求參數,請求頭,以及錯誤提示資訊等。具體可以參看 request.js。 它封裝了全局 request攔截器、respone攔截器、統一的錯誤處理、統一做了逾時,baseURL設定等
4 企業管理
4.1 需求分析
在通用頁面配置企業管理子產品,完成企業的基本操作
4.2 搭建環境
4.2.1 新增子產品
(1)手動建立
方式一:在src目錄下建立檔案夾,命名規則:module-子產品名稱()
在檔案夾下按照指定的結構配置assets,components,pages,router,store等檔案
(2)使用指令自動建立
安裝指令行工具:npm install -g itheima-cli
執行指令:itheima moduleAdd saas-clients `saas-clients` 是新子產品的名字
自動建立這些目錄和檔案
│ ├── module-saas-clients | saas-clients子產品主目錄
│ │ ├── assets | 資源
│ │ ├── components | 元件
│ │ ├── pages | 頁面
│ │ │ └── index.vue | 示例
│ │ ├── router | 路由
│ │ │ └── index.js | 示例
│ │ └── store | 資料
│ │ └── app.js | 示例
每個子產品所有的素材、頁面、元件、路由、資料,都是獨立的,友善大型項目管理,
在實際項目中會有很多子業務項目,它們之間的關系是平行的、低耦合、互不依賴。
注意:建立完子產品之後,導緻名稱和demo子產品一樣,是以需要修改module-demo/router下面的index.js:
4.2.2 構造模拟資料
(1)在/src/mock 中添加模拟資料company.js
import Mock from 'mockjs'
import { param2Obj } from '@/utils'
const List = []
const count = 100
for (let i = 0; i < 3; i++) {
let data = {
id: "1"+i,
name: "企業"+i,
managerId: "string",
version: "試用版v1.0",
renewalDate: "2018-01-01",
expirationDate: "2019-01-01",
companyArea: "string",
companyAddress: "string",
businessLicenseId: "string",
legalRepresentative: "string",
companyPhone: "13800138000",
mailbox: "string",
companySize: "string",
industry: "string",
remarks: "string",
auditState: "string",
state: "1",
balance: "string",
createTime: "string"
}
List.push(data)
}
export default {
list: () => {
return {
code: 10000,
success: true,
message: "查詢成功",
data:List
}
},
sassDetail:() => {
return {
code: 10000,
success: true,
message: "查詢成功",
data:{
id: "10001",
name: "測試企業",
managerId: "string",
version: "試用版v1.0",
renewalDate: "2018-01-01",
expirationDate: "2019-01-01",
companyArea: "string",
companyAddress: "string",
businessLicenseId: "string",
legalRepresentative: "string",
companyPhone: "13800138000",
mailbox: "string",
companySize: "string",
industry: "string",
remarks: "string",
auditState: "string",
state: "1",
balance: "string",
createTime: "string"
}
}
}
}
(2)配置模拟API接口攔截規則
在/src/mock/index.js 中配置模拟資料接口攔截規則
import Mock from 'mockjs'
import TableAPI from './table'
import ProfileAPI from './profile'
import LoginAPI from './login'
import CompanyAPI from './company'
Mock.setup({
//timeout: '1000'
})
//如果發送請求的api路徑比對,攔截
//第一個參數比對的請求api路徑,第二個參數比對請求的方式,第三個參數相應資料如何替換
Mock.mock(/\/table\/list\.*/, 'get', TableAPI.list)
//擷取使用者資訊
Mock.mock(/\/frame\/profile/, 'post', ProfileAPI.profile)
Mock.mock(/\/frame\/login/, 'post', LoginAPI.login)
//配置模拟資料接口
Mock.mock(/\/company\/+/, 'get', CompanyAPI.sassDetail)//根據id查詢
Mock.mock(/\/company/, 'get', CompanyAPI.list) //通路企業清單
4.2.3 注冊子產品
編輯 src/main.js
/*
* 注冊 - 業務子產品
*/
import dashboard from '@/module-dashboard/' // 面闆
import demo from '@/module-demo/' // 面闆
import saasClients from '@/module-saas-clients/' //剛新添加的 企業管理
import tools from './utils/common.js'
Vue.prototype.$tools = tools
Vue.use(tools)
Vue.use(dashboard, store)
Vue.use(demo, store)
Vue.use(saasClients, store) ///注冊 剛新添加的 企業管理
4.2.4 配置路由菜單
打開剛才自動建立的 /src/module-saas-clients/router/index.js
/*
* @Author: dongwen.zeng <[email protected]>
* @Description: xxx業務子產品
* @Date: 2018-04-13 16:13:27
* @Last Modified by: hans.taozhiwei
* @Last Modified time: 2018-09-03 11:12:47
*/
import Layout from '@/module-dashboard/pages/layout'
const _import = require('@/router/import_' + process.env.NODE_ENV)
export default [
{
root: true,
path: '/saas-clients',
component: Layout,
redirect: 'noredirect',
name: 'saas-clients',
meta: {
title: 'xxx業務子產品管理',
icon: 'international'
},
children: [
{
path: 'index',
component: _import('saas-clients/pages/index'),
name: 'saas-clients-index',
meta: {title: 'SaaS企業管理', icon: 'international', noCache: true}
}
]
}
]
4.2.5 編寫業務頁面
建立 /src/module-saas-clients/pages/index.vue
<template>
<div class="dashboard-container">
saas企業管理
</div>
</template>
<script>
export default {
name: 'saasClintList',
components: {},
data() {
return {
}
},
computed: {
},
created() {
}
}
</script>
注意檔案名 駝峰格式 首字小寫
頁面請放在目錄 /src/module-saas-clients/pages/
元件請放在目錄 /src/module-saas-clients/components/
頁面路由請修改 /src/module-saas-clients/router/index.js
4.3 企業操作
4.3.1 建立api
在src/api/base目錄下建立企業資料互動的API(saasClient.js)
import {createAPI, createFormAPI} from '@/utils/request' //導入相關工具類,架構自己提供的
//第一個參數/company是請求路徑(路徑可以是完全路徑,也可以是部分路徑,因為我們在config/dev.env.js下面有字首配置),
// BASE_API: '"http://localhost:9001/"' 第二參數是請求方式,第三個參數請求的參數資料
export const list = data => createAPI('/company', 'get', data)
// data代表請求的對象,${data.id}表示從請求的對象中取出id屬性
export const detail = data => createAPI(`/company/${data.id}`, 'get', data)
4.3.2 企業清單
<template>
<div class="dashboard-container">
<div class="app-container">
<el-card shadow="never">
<!--elementui的table元件
data:資料模型
-->
<el-table :data="dataList" border style="width: 100%">
<!--el-table-column : 構造表格中的每一列
prop: 數組中每個元素對象的屬性名
-->
<el-table-column fixed type="index" label="序号" style="width:50px" ></el-table-column>
<el-table-column fixed prop="name" label="企業名稱" style="width:100px"></el-table-column>
<el-table-column fixed prop="version" label="版本" style="width:30px"></el-table-column>
<el-table-column fixed prop="companyphone" label="聯系電話" style="width:100px">
</el-table-column>
<el-table-column fixed prop="expirationDate" label="截至時間" style="width:150px">
</el-table-column>
<el-table-column fixed prop="state" label="狀态" style="width:50px">
<!--scope:傳遞目前行的所有資料 -->
<template slot-scope="scope">
<!--開關元件
active-value:激活的資料值
active-color:激活的顔色
inactive-value:未激活
inactive-color:未激活的顔色
-->
<el-switch v-model="scope.row.state" inactive-value="0" active-value="1" disabled
active-color="#13ce66" inactive-color="#ff4949">
</el-switch>
</template>
</el-table-column>
<el-table-column fixed="right" label="操作" style="width:100px">
<template slot-scope="scope">
<router-link :to="'/saas-clients/details/'+scope.row.id">檢視</router-link>
</template>
</el-table-column>
</el-table>
</el-card>
</div>
</div>
</template>
<script>
import {list} from '@/api/base/saasClient' //導入list方法,從自定義的api中
export default {
name: 'saas-clients-index',
components: {},
data() {
return {
dataList:[]
}
},
methods: {
getList() {
//調用API發起請求
//res=響應資料
list().then(res => {
this.dataList = res.data.data
})
}
},
created() {
this.getList()
}
}
</script>
<style rel="stylesheet/scss" scoped>
.alert {
margin: 10px 0px 0px 0px;
}
.pagination {
margin-top: 10px;
text-align: right;
}
</style>
4.3.3 企業詳情
(1)配置路由
在/src/module-saas-clients/router/index.js 添加新的子路由配置
{
path: 'details/:id', //特别注意這個路徑的寫法
component: _import('saas-clients/pages/details'),
name: 'saas-clients-details',
meta: {title: 'saas企業詳情', icon: 'component', noCache: true}
}
(2)完成詳情展示
在/src/module-saas-clients/pages/ 下建立企業詳情視圖details.vue
<template>
<div class="dashboard-container">
<div class="app-container">
<el-card shadow="never">
<el-tabs v-model="activeName">
<!--第一個頁簽的内容-->
<el-tab-pane label="企業資訊" name="first">
<!--form表單
model : 雙向綁定的資料對象
-->
<el-form ref="form" :model="company" label-width="200px">
<el-form-item label="企業名稱" >
<el-input v-model="company.name" style="width:400px" disabled></el-input>
</el-form-item>
<el-form-item label="公司位址">
<el-input v-model="company.companyAddress" style="width:400px" disabled></el-input>
</el-form-item>
<el-form-item label="法人代表">
<el-input v-model="company.legalRepresentative" style="width:400px" disabled></el-input>
</el-form-item>
<el-form-item label="目前餘額">
<el-input v-model="company.balance" style="width:400px" disabled></el-input>
</el-form-item>
<el-form-item label="建立時間">
<el-input v-model="company.createTime" style="width:400px" disabled></el-input>
</el-form-item>
<el-form-item label="公司電話">
<el-input v-model="company.companyPhone" style="width:400px" disabled></el-input>
</el-form-item>
<el-form-item label="郵箱">
<el-input v-model="company.mailbox" style="width:400px" disabled></el-input>
</el-form-item>
<el-form-item label="備注">
<el-input v-model="company.remark" style="width:400px" ></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary">稽核</el-button>
<el-button>拒絕</el-button>
</el-form-item>
</el-form>
</el-tab-pane>
<!--第一個頁簽的内容結束-->
<el-tab-pane label="賬戶資訊" name="second">賬戶資訊</el-tab-pane>
<el-tab-pane label="交易記錄" name="third">交易記錄</el-tab-pane>
</el-tabs>
</el-card>
</div>
</div>
</template>
<script>
import {details} from '@/api/base/saasClient'
export default {
name: 'saas-clients-table-details',
data() {
return {
activeName: 'first', //定義預設顯示第一個頁簽
company: {}
}
},
methods: {
details(id){
//調用api方法查詢公司詳細資訊
details({id:id}).then(res => {
this.company = res.data.data;
console.log(id);
console.log(res.data.data)
});
}
},
// 建立完畢狀态
created() {
var id = this.$route.params.id //擷取路徑上的參數id的值
this.details(id);
},
}
</script>
<style rel="stylesheet/scss" scoped>
.alert {
margin: 10px 0px 0px 0px;
}
.pagination {
margin-top: 10px;
text-align: right;
}
</style>
4.4 與背景對接測試
(1)啟動第一天的企業微服務服務(ihrm_company);
(2)注釋掉src/mock目錄下index.js下面的:
//配置模拟資料接口
//Mock.mock(/\/company\/+/, 'get', CompanyAPI.sassDetail)//根據id查詢
//Mock.mock(/\/company/, 'get', CompanyAPI.list) //通路企業清單
(3)在config/dev.env.js 中配置請求位址
'use strict'
const merge = require('webpack-merge')
const prodEnv = require('./prod.env')
module.exports = merge(prodEnv, {
NODE_ENV: '"development"',
BASE_API: '"http://localhost:9001/"'
})