利用 Vely 在 Linux 構(gòu)建你自己的 SaaS
Vely 將 C 語(yǔ)言的高性能和低內(nèi)存占用與 PHP 等語(yǔ)言的易用性和安全性相結(jié)合。作為自由開(kāi)源軟件,它以 GPLv3 和 LGPL 3 授權(quán),所以你甚至可以用它來(lái)構(gòu)建商業(yè)軟件。
利用 Vely 構(gòu)建 SaaS
你可以使用 Vely 創(chuàng)建一個(gè)多租戶網(wǎng)絡(luò)應(yīng)用程序,它可以作為軟件即服務(wù)模式(SaaS)在互聯(lián)網(wǎng)上運(yùn)行。每個(gè)用戶都有一個(gè)完全獨(dú)立的數(shù)據(jù)空間。
在這個(gè)網(wǎng)絡(luò)應(yīng)用程序示例中,用戶可以注冊(cè)一個(gè)筆記本服務(wù)來(lái)創(chuàng)建筆記,然后查看和刪除它們。它僅用了 7 個(gè)源文件,310 行代碼,就展示了如何集成多項(xiàng)技術(shù):
- MariaDB
- 網(wǎng)絡(luò)瀏覽器
- Apache
- Unix 套接字
運(yùn)作原理
以下是從用戶的角度來(lái)看應(yīng)用程序是如何工作的。下圖是代碼演示。
該應(yīng)用允許用戶通過(guò)指定電子郵件地址和密碼創(chuàng)建新的登錄名。你可以用任何你喜歡的方式設(shè)置它們,例如運(yùn)用 CSS:
創(chuàng)建一個(gè)用戶賬戶
驗(yàn)證用戶的電子郵件:
驗(yàn)證用戶的電子郵件地址
每個(gè)用戶使用自己獨(dú)有的用戶名和密碼登錄:
用戶登錄
一旦登錄,用戶就可以添加筆記:
用戶可以添加筆記
用戶可以獲取筆記列表:
用戶列舉筆記
刪除筆記之前,應(yīng)用會(huì)申請(qǐng)確認(rèn)信息:
刪除筆記之前,應(yīng)用會(huì)申請(qǐng)確認(rèn)信息
用戶確認(rèn)后,筆記被刪除:
用戶確認(rèn)后,筆記被刪除
設(shè)置先決條件
遵照 Vely.dev 上的安裝指示。這是一個(gè)使用 DNF、APT、Pacman 或者 Zypper 等標(biāo)準(zhǔn)工具包的快速流程。
由于它們都是這個(gè)范例的一部分,你必須安裝 Apache 作為網(wǎng)絡(luò)服務(wù)器,安裝 MariaDB 作為數(shù)據(jù)庫(kù)。
安裝 Vely 后,如使用 Vim,打開(kāi)里面的“語(yǔ)法高亮顯示”:
vv -m
獲取源代碼
這個(gè)演示 SaaS 應(yīng)用程序的源代碼是 Vely 安裝的一部分。為每個(gè)應(yīng)用程序創(chuàng)建一個(gè)單獨(dú)的源代碼目錄不失為一個(gè)好主意(而且你可以按自己喜好命名)。在這種情況下,解包源代碼會(huì)幫你完成這些工作:
$ tar xvf $(vv -o)/examples/multitenant_SaaS.tar.gz
$ cd multitenant_SaaS
默認(rèn)情況下,該應(yīng)用程序以 multitenant_SaaS
命名,但你可以將其命名為任何內(nèi)容(如果這么做,其他每個(gè)地方你都需要改一下)。
創(chuàng)建應(yīng)用程序
第一步是創(chuàng)建一個(gè)應(yīng)用程序。使用 Vely 的 vf
工具就可以輕松完成:
$ sudo vf -i-u $(whoami) multitenant_SaaS
這個(gè)命令創(chuàng)建了一個(gè)新的應(yīng)用程序主目錄(/var/lib/vv/multitenant_SaaS
),并幫你執(zhí)行應(yīng)用程序設(shè)置。通常,這意味著在該主目錄中創(chuàng)建各種子目錄并分配權(quán)限。在這種情況下,只有當(dāng)前用戶(whoami
的結(jié)果)擁有目錄,具有 0700
權(quán)限,這確保了其他人沒(méi)有訪問(wèn)文件的權(quán)限。
創(chuàng)建數(shù)據(jù)庫(kù)
在你鍵入任何代碼之前,你需要一個(gè)能夠存儲(chǔ)該應(yīng)用程序所用信息的空間。首先,創(chuàng)建一個(gè)名為 db_multitenant_SaaS
的 MariaDB 數(shù)據(jù)庫(kù),由用戶名為 vely
的用戶所有,密碼為 your_password
。你可以修改剛才提到的任何值,但得記住,在這個(gè)示例里,你需要將包含這些內(nèi)容的每個(gè)地方都得修改一遍。
在 MySQL 中以 root 身份登錄:
create database if not exists db_multitenant_SaaS;
create user if not exists vely identified by 'your_password';
grant create,alter,drop,select,insert,delete,update on db_multitenant_SaaS.* to vely;
然后在數(shù)據(jù)庫(kù)內(nèi)創(chuàng)建數(shù)據(jù)庫(kù)對(duì)象(表,記錄等等):
use db_multitenant_SaaS;
source setup.sql;
exit
將 Vely 連接至數(shù)據(jù)庫(kù)
為了讓 Vely 知曉你數(shù)據(jù)庫(kù)的位置以及如何登錄進(jìn)去,創(chuàng)建一個(gè)名為 db_multitenant_SaaS
的數(shù)據(jù)庫(kù)配置文件。(該名稱用于在源代碼中的數(shù)據(jù)庫(kù)聲明,所以如果你改了它,確保在它存在的每個(gè)地方都改一遍。)
Vely 使用原生的 MariaDB 數(shù)據(jù)庫(kù)連接,因此你可以指定給定的數(shù)據(jù)庫(kù)所能允許的任何選項(xiàng):
$ echo '[client]
user=vely
password=your_password
database=db_multitenant_SaaS
protocol=TCP
host=127.0.0.1
port=3306' > db_multitenant_SaaS
構(gòu)建應(yīng)用程序
使用 vv
工具構(gòu)建應(yīng)用程序,利用 --db
選項(xiàng)指定 MariaDB 數(shù)據(jù)庫(kù)和數(shù)據(jù)庫(kù)配置文件:
$ vv -q--db=mariadb:db_multitenant_SaaS
啟用應(yīng)用服務(wù)器
啟動(dòng)你的網(wǎng)絡(luò)應(yīng)用程序的服務(wù)器,需要使用 vf
FastCGI 進(jìn)程管理器。應(yīng)用程序服務(wù)器使用 Unix 套接字與網(wǎng)絡(luò)服務(wù)器(創(chuàng)建反向代理)通信:
$ vf -w3 multitenant_SaaS
這么做會(huì)啟用三個(gè)守護(hù)進(jìn)程,為接收到的請(qǐng)求提供服務(wù)。你也可以啟動(dòng)一個(gè)自適應(yīng)服務(wù)器,它會(huì)增加進(jìn)程的數(shù)量從而服務(wù)更多的請(qǐng)求,并在不需要他們時(shí)減少進(jìn)程的數(shù)量:
$ vf multitenant_SaaS
請(qǐng)參閱 vf
了解更多選項(xiàng),以幫助你實(shí)現(xiàn)最佳性能。
當(dāng)你需要停止你的應(yīng)用程序服務(wù)器,使用 -m quit
選項(xiàng):
$ vf -m quit multitenant_SaaS
創(chuàng)建網(wǎng)絡(luò)服務(wù)器
這是一個(gè)網(wǎng)絡(luò)應(yīng)用程序,那么應(yīng)用程序就得需要一個(gè)網(wǎng)絡(luò)服務(wù)器。該示例通過(guò)一個(gè) Unix 套接字監(jiān)聽(tīng)器使用 Apache。
1、設(shè)置 Apache
將 Apache 配置為一個(gè)反向代理,并將你的應(yīng)用程序與之連接,你需要啟用 FastCGI 代理支持,這通常使用 proxy
和 proxy_fcgi
模塊。
對(duì)于 Fedora 系統(tǒng)(或者其它的,比如 Arch)來(lái)說(shuō),通過(guò)在 Apache 配置文件 /etc/httpd/conf/httpd.conf
中添加(或取消注釋)適當(dāng)?shù)?nbsp;LoadModule
指令,就可啟用 proxy
和 proxy_fcgi
模塊。
以下指令適用于 Debian,Ubuntu 以及類似的系統(tǒng),啟用 proxy
和 proxy_fcgi
模塊:
$ sudo a2enmod proxy
$ sudo a2enmod proxy_fcgi
以下指令適用于 OpenSUSE,將這幾行添加在 /etc/apache2/httpd.conf
結(jié)尾處:
LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_fcgi_module modules/mod_proxy_fcgi.so
2、配置 Apache
現(xiàn)在你必須將代理信息添加在 Apache 的配置文件中:
ProxyPass "/multitenant_SaaS" unix:///var/lib/vv/multitenant_SaaS/sock/sock|fcgi://localhost/multitenant_SaaS
你的配置文件的位置可能會(huì)有所不同,這取決于不同的 Linux 發(fā)行版:
- Fedora、CentOS、Mageia 和 Arch:
/etc/httpd/conf/httpd.conf
- Debian、Ubuntu、Mint:
/etc/apache2/apache2.conf
- OpenSUSE:
/etc/apache2/httpd.conf
3、重新啟動(dòng)
最后,重啟 Apache。在 Fedora 和類似系統(tǒng),還有 Arch Linux 是如下指令:
$ sudo systemctl restart httpd
在 Debian 和基于 Debian 的系統(tǒng),還有 OpenSUSE 是如下指令:
$ sudo systemctl restart apache2
設(shè)置本地郵箱
這個(gè)示例中,電子郵件是其功能的一部分。如果你的服務(wù)器已經(jīng)可以發(fā)送電子郵件了,你可以跳過(guò)這一條。此外,你可以使用本地郵箱(myuser@localhost
)來(lái)測(cè)試它。要做到這一點(diǎn),需安裝 Sendmail。
在 Fedora 和類似系統(tǒng)中是如下指令:
$ sudo dnf installsendmail
$ sudo systemctl start sendmail
而在 Debian 和類似系統(tǒng)(如 Ubuntu):
$ sudo apt installsendmail
$ sudo systemctl start sendmail
當(dāng)應(yīng)用程序向本地用戶發(fā)送電子郵件,比如說(shuō) OS_user@localhost
,你就可以通過(guò)查看 /var/mail/
處(即所謂“郵件池”)來(lái)確認(rèn)電子郵件是否被發(fā)送。
從瀏覽器訪問(wèn)應(yīng)用服務(wù)器
假設(shè)你在本地運(yùn)行該應(yīng)用,可以通過(guò)使用 http://127.0.0.1/multitenant_SaaS?req=notes&action=begin
域名從你的網(wǎng)絡(luò)服務(wù)器訪問(wèn)你的應(yīng)用服務(wù)器。如果你在互聯(lián)網(wǎng)上的在線服務(wù)器運(yùn)行該程序,你可能就需要調(diào)整防火墻設(shè)置以允許 HTTP 通信。
源代碼
該應(yīng)用程序示例包含 7 個(gè)源文件。你可以自行回顧代碼(記住,這些文件只有 310 行代碼),下面是每個(gè)文件的概述。
SQL 設(shè)置(setup.sql)
創(chuàng)建的兩個(gè)表:
users
:每個(gè)用戶的信息。在users
表中,每個(gè)用戶都有自己唯一的 ID (userId
列),以及其他信息,如電子郵件地址和該地址是否通過(guò)了驗(yàn)證。還有一個(gè)哈希密碼。實(shí)際的密碼永遠(yuǎn)不會(huì)存儲(chǔ)在純文本(或其他形式)中,單向哈希用于檢查密碼。notes
:用戶輸入的筆記。notes
表包含了所有的筆記,每個(gè)筆記都有一個(gè)userId
列,表示哪個(gè)用戶擁有它們。userId
列的值與users
表中的同名列匹配。這樣,每個(gè)筆記顯然都屬于單個(gè)用戶。
該文件內(nèi)容如下:
create table if not exists notes (dateOf datetime, noteId bigint auto_increment primary key, userId bigint, note varchar(1000));
create table if not exists users (userId bigint auto_increment primary key, email varchar(100), hashed_pwd varchar(100), verified smallint, verify_token varchar(30), session varchar(100));
create unique index if not exists users1 on users (email);
運(yùn)行時(shí)數(shù)據(jù)(login.h)
為了正確地顯示登錄、注冊(cè)和注銷鏈接,你需要一些在應(yīng)用程序中任何地方都可以使用的標(biāo)志。此外,應(yīng)用程序使用 cookie 來(lái)維護(hù)會(huì)話,因此它需要在任何地方都可用,例如,驗(yàn)證會(huì)話是否有效。發(fā)送到應(yīng)用程序的每個(gè)請(qǐng)求都以這種方式進(jìn)行確認(rèn)。只有帶有可驗(yàn)證 cookie 的請(qǐng)求是允許的。
所以要做到這種效果,你需要有一個(gè) global_request_data
類型的 reqdata
(請(qǐng)求數(shù)據(jù)),其中包含 sess_userId
(用戶的 ID)以及 sess_id
(用戶目前的會(huì)話 ID)。此外,還有一些不言自明的標(biāo)志,可以幫助渲染頁(yè)面:
#ifndef _VV_LOGIN
#define _VV_LOGIN
typedef struct s_reqdata {
bool displayed_logout; // true 則顯示登出連接
bool is_logged_in; // true 則會(huì)話已驗(yàn)證登錄
char *sess_userId; // 目前會(huì)話的用戶 ID
char *sess_id; // 會(huì)話 ID
} reqdata;
void login_or_signup ();
#endif
會(huì)話檢查和會(huì)話數(shù)據(jù)(_before.vely)
Vely 里有一個(gè) 請(qǐng)求前處理程序before_request handler 的概念。你寫(xiě)的代碼會(huì)在其它處理請(qǐng)求的代碼之前執(zhí)行的。要達(dá)到這個(gè)目的,你只需要將這樣的代碼寫(xiě)在名為 _before.vely
的文件中,然后剩余的部分將會(huì)自動(dòng)處理。
SaaS 應(yīng)用程序所作的任何事情,例如處理發(fā)送至應(yīng)用程序的請(qǐng)求,必須驗(yàn)證其安全性。這樣,應(yīng)用程序就能知曉調(diào)用方是否有執(zhí)行操作所需要的權(quán)限。
在這里,通過(guò)請(qǐng)求前處理程序進(jìn)行權(quán)限檢查。這樣,無(wú)論其他代碼如何處理請(qǐng)求,都已經(jīng)掌握了會(huì)話信息。
為保持會(huì)話數(shù)據(jù)(比如會(huì)話 ID 和用戶 ID)在你代碼中的任何地方都可用,你可以使用 global_request_data
。它只是一個(gè)指向內(nèi)存的通用指針(void*
),任何處理請(qǐng)求的代碼都可以訪問(wèn)它。這非常適合處理會(huì)話,如下所示:
#include "vely.h"
#include "login.h"
// _before() 是一個(gè)請(qǐng)求前處理程序。
// 它總是在處理請(qǐng)求的其他代碼之前執(zhí)行。
// 對(duì)于任何類型的請(qǐng)求范圍設(shè)置或數(shù)據(jù)初始化,它都是一個(gè)很好的位置。
void _before() {
// 輸出 HTTP 請(qǐng)求頭
out-header default
reqdata *rd; // 這是全局請(qǐng)求數(shù)據(jù),見(jiàn) login.h
// 為全局請(qǐng)求數(shù)據(jù)分配內(nèi)存,
// 將在請(qǐng)求結(jié)束時(shí)自動(dòng)釋放
new-mem rd size sizeof(reqdata)
// 初始化標(biāo)志
rd->displayed_logout = false;
rd->is_logged_in = false;
// 將我們創(chuàng)建的數(shù)據(jù)設(shè)置為全局請(qǐng)求數(shù)據(jù),
// 可以從任何處理請(qǐng)求的代碼中訪問(wèn)
set-req data rd
// 檢查會(huì)話是否存在(基于來(lái)自客戶端的 cookie)
// 這在任何其他請(qǐng)求處理代碼之前執(zhí)行,
// 使其更容易準(zhǔn)備好會(huì)話信息
_check_session ();
}
檢查會(huì)話是否有效(_check_session.vely)
多租戶 SaaS 應(yīng)用程序中最重要的任務(wù)之一就是通過(guò)檢查用戶是否登錄來(lái)(盡快)檢查會(huì)話是否有效。這是通過(guò)從客戶端(例如網(wǎng)絡(luò)瀏覽器)獲取會(huì)話 ID 和用戶 ID 的 cookie,并將它們與存儲(chǔ)會(huì)話的數(shù)據(jù)庫(kù)進(jìn)行比較來(lái)實(shí)現(xiàn)的:
#include "vely.h"
#include "login.h"
// 檢查會(huì)話是否有效
void _check_session () {
// 獲取全局請(qǐng)求數(shù)據(jù)
reqdata *rd;
get-req data to rd
// 自用戶瀏覽器獲取 cookies
get-cookie rd->sess_userId="sess_userId"
get-cookie rd->sess_id="sess_id"
if (rd->sess_id[0] != 0) {
// 檢查給定用戶 ID 下的會(huì)話 ID 是否正確
char *email;
run-query @db_multitenant_SaaS = "select email from users where userId='%s' and session='%s'" output email : rd->sess_userId, rd->sess_id row-count define rcount
query-result email to email
end-query
if (rcount == 1) {
// 如果正確,設(shè)置登錄標(biāo)志
rd->is_logged_in = true;
// 如果登出鏈接不顯示,則顯示它
if (rd->displayed_logout == false) {
@Hi <<p-out email>>! <a >Logout</a><br/>
rd->displayed_logout = true;
}
} else rd->is_logged_in = false;
}
}
注冊(cè)、登錄、登出(login.vely)
任何多租戶系統(tǒng)的基礎(chǔ)便是具有用戶注冊(cè)\登錄和登出的功能。通常情況下,注冊(cè)包括驗(yàn)證電子郵件地址;不止于此,同一電子郵件地址會(huì)作為一個(gè)用戶名。這里就是這種情況。
這里實(shí)現(xiàn)了幾個(gè)執(zhí)行該功能所必須的子請(qǐng)求:
- 注冊(cè)新用戶時(shí),顯示 HTML 表單以收集信息。它的 URL 請(qǐng)求簽名是
req=login&action=newuser
。 - 作為對(duì)注冊(cè)表單的響應(yīng),創(chuàng)建一個(gè)新用戶。URL 請(qǐng)求的簽名是
req=login&action=createuser
。輸入?yún)?shù)(input-param
)信號(hào)獲取email
和pwd
的 POST 表單字段。密碼值是單向散列,電子郵件驗(yàn)證令牌是一個(gè)隨機(jī)的 5 位數(shù)字。這些被插入到users
表中,創(chuàng)建一個(gè)新用戶。系統(tǒng)會(huì)發(fā)送一封驗(yàn)證郵件,并提示用戶閱讀郵件并輸入代碼。 - 通過(guò)輸入發(fā)送到該電子郵件的驗(yàn)證碼來(lái)驗(yàn)證電子郵件。URL 請(qǐng)求的簽名是
req=login&action=verify
。 - 顯示一個(gè)登錄表單,讓用戶登錄。URL 請(qǐng)求的簽名是
req=login
(例如,action
為空)。 - 通過(guò)驗(yàn)證電子郵件地址(用戶名)和密碼登錄。URL 請(qǐng)求的簽名是
req=login&action=login
。 - 應(yīng)用戶要求登出。URL 請(qǐng)求的簽名是
req=login&action=logout
。 - 應(yīng)用程序的登錄頁(yè)。URL 請(qǐng)求的簽名是
req=login&action=begin
。 - 如果用戶當(dāng)前已登錄,轉(zhuǎn)到應(yīng)用程序的登錄頁(yè)面。
可以看看下面這些例子:
#include "vely.h"
#include "login.h"
// 處理云端多租戶應(yīng)用程序的會(huì)話維護(hù)、登錄、注銷、會(huì)話驗(yàn)證
void login () {
// 獲取 URL 的輸入?yún)?shù) `action`
input-param action
// 獲取全局請(qǐng)求數(shù)據(jù),我們?cè)谄渲杏涗洉?huì)話信息,所以它很方便
reqdata *rd;
get-req data to rd
// 如果會(huì)話已經(jīng)建立,我們不會(huì)
// 繼續(xù)到應(yīng)用程序主頁(yè)的唯一原因是我們正在登出
if (rd->is_logged_in) {
if (strcmp(action, "logout")) {
_show_home();
exit-request
}
}
// 應(yīng)用程序頁(yè)面啟動(dòng)。顯示登錄或注冊(cè)的鏈接,
// 并顯示適當(dāng)?shù)闹髌聊? if (!strcmp (action, "begin")) {
_show_home();
exit-request
// 開(kāi)始創(chuàng)建新用戶。詢問(wèn)電子郵件和密碼,
// 然后提交此表單時(shí)創(chuàng)建用戶。
} else if (!strcmp (action, "newuser")) {
@Create New User<hr/>
@<form action="https://opensource.com/?req=login" method="POST">
@<input name="action" type="hidden" value="createuser">
@<input name="email" type="text" value="" size="50" maxlength="50" required autofocus placeholder="Email">
@<input name="pwd" type="password" value="" size="50" maxlength="50" required placeholder="Password">
@<input type="submit" value="Sign Up">
@</form>
// 驗(yàn)證用戶發(fā)送到電子郵件的代碼。代碼必須匹配,從而驗(yàn)證電子郵件地址
} else if (!strcmp (action, "verify")) {
input-param code
input-param email
// 獲取基于電子郵件的驗(yàn)證令牌
run-query @db_multitenant_SaaS = "select verify_token from users where email='%s'" output db_verify : email
query-result db_verify to define db_verify
// 將數(shù)據(jù)庫(kù)中記錄的令牌與用戶提供的令牌進(jìn)行比較
if (!strcmp (code, db_verify)) {
@Your email has been verifed. Please <a >Login</a>.
// 如果匹配,更新用戶信息以表明已驗(yàn)證。
run-query @db_multitenant_SaaS no-loop = "update users set verified=1 where email='%s'" : email
exit-request
}
end-query
@Could not verify the code. Please try <a >again</a>.
exit-request
// 創(chuàng)建用戶 —— 當(dāng)用戶使用電子郵件和密碼提交表單以創(chuàng)建用戶時(shí)運(yùn)行
} else if (!strcmp (action, "createuser")) {
input-param email
input-param pwd
// 創(chuàng)建散列(單向)密碼
hash-string pwd to define hashed_pwd
// 生成隨機(jī)的 5 位數(shù)字字符串驗(yàn)證代碼
random-string to define verify length 5 number
// 創(chuàng)建用戶:插入電子郵件、哈希密碼、驗(yàn)證令牌。當(dāng)前驗(yàn)證狀態(tài)為 0,或未驗(yàn)證
begin-transaction @db_multitenant_SaaS
run-query @db_multitenant_SaaS no-loop = "insert into users (email, hashed_pwd, verified, verify_token, session) values ('%s', '%s', '0', '%s', '')" : email, hashed_pwd, verify affected-rows define arows error define err on-error-continue
if (strcmp (err, "0") || arows != 1) {
// 如果不能添加用戶,則可能該用戶不存在。不管怎樣,我們都無(wú)法繼續(xù)。
login_or_signup();
@User with this email already exists.
rollback-transaction @db_multitenant_SaaS
} else {
// 創(chuàng)建帶有驗(yàn)證碼的電子郵件并將其發(fā)送給用戶
write-string define msg
@From: vely@vely.dev
@To: <<p-out email>>
@Subject: verify your account
@
@Your verification code is: <<p-out verify>>
end-write-string
exec-program "/usr/sbin/sendmail" args "-i", "-t" input msg status define st
if (st != 0) {
@Could not send email to <<p-out email>>, code is <<p-out verify>>
rollback-transaction @db_multitenant_SaaS
exit-request
}
commit-transaction @db_multitenant_SaaS
// 通知用戶查看郵件并輸入驗(yàn)證碼
@Please check your email and enter verification code here:
@<form action="https://opensource.com/?req=login" method="POST">
@<input name="action" type="hidden" value="verify" size="50" maxlength="50">
@<input name="email" type="hidden" value="<<p-out email>>">
@<input name="code" type="text" value="" size="50" maxlength="50" required autofocus placeholder="Verification code">
@<button type="submit">Verify</button>
@</form>
}
// 這里在登錄用戶登出時(shí)運(yùn)行
} else if (!strcmp (action, "logout")) {
// 更新用戶表以清除會(huì)話,即沒(méi)有該用戶登錄
if (rd->is_logged_in) {
run-query @db_multitenant_SaaS = "update users set session='' where userId='%s'" : rd->sess_userId no-loop affected-rows define arows
if (arows == 1) {
rd->is_logged_in = false; // 提示用戶未登錄
@You have been logged out.<hr/>
}
}
_show_home();
// 登錄:當(dāng)用戶輸入用戶名和密碼時(shí)運(yùn)行
} else if (!strcmp (action, "login")) {
input-param pwd
input-param email
// 創(chuàng)建單向散列,目的是與用戶表進(jìn)行比較 —— 密碼**永遠(yuǎn)不會(huì)**被記錄
hash-string pwd to define hashed_pwd
// 為會(huì)話 ID 創(chuàng)建一個(gè)隨機(jī)的 30 位長(zhǎng)的字符串
random-string to rd->sess_id length 30
// 檢查用戶名和哈希密碼是否匹配
run-query @db_multitenant_SaaS = "select userId from users where email='%s' and hashed_pwd='%s'" output sess_userId : email, hashed_pwd
query-result sess_userId to rd->sess_userId
// 如果匹配,使用會(huì)話 ID 更新用戶表
run-query @db_multitenant_SaaS no-loop = "update users set session='%s' where userId='%s'" : rd->sess_id, rd->sess_userId affected-rows define arows
if (arows != 1) {
@Could not create a session. Please try again. <<.login_or_signup();>> <hr/>
exit-request
}
// 設(shè)置“用戶 ID”和“會(huì)話 ID”為 cookie。用戶的瀏覽器將在每個(gè)請(qǐng)求中返回這些信息
set-cookie "sess_userId" = rd->sess_userId
set-cookie "sess_id" = rd->sess_id
// 顯示主頁(yè),確保會(huì)話是正確的,并設(shè)置標(biāo)志
_check_session();
_show_home();
exit-request
end-query
@Email or password are not correct. <<.login_or_signup();>><hr/>
// 登錄界面,要求用戶輸入用戶名和密碼
} else if (!strcmp (action, "")) {
login_or_signup();
@Please Login:<hr/>
@<form action="https://opensource.com/?req=login" method="POST">
@<input name="action" type="hidden" value="login" size="50" maxlength="50">
@<input name="email" type="text" value="" size="50" maxlength="50" required autofocus placeholder="Email">
@<input name="pwd" type="password" value="" size="50" maxlength="50" required placeholder="Password">
@<button type="submit">Go</button>
@</form>
}
}
// 顯示登錄或注冊(cè)鏈接
void login_or_signup() {
@<a >Login</a> & & <a >Sign Up</a><hr/>
}
通用應(yīng)用程序(_show_home.vely)
借助本教程,你可以創(chuàng)建你想要的任何多租戶 SaaS 應(yīng)用程序。上面的多租戶處理模塊(login.vely
)調(diào)用 _show_home()
函數(shù),它可以容納你的任何代碼。這個(gè)示例代碼展示了筆記應(yīng)用程序,但它可以是任何內(nèi)容。_show_home()
函數(shù)可以調(diào)用你想要的任何代碼,它是一個(gè)通用的多租戶應(yīng)用程序插件:
#include "vely.h"
void _show_home() {
notes();
exit-request
}
筆記應(yīng)用程序(notes.vely)
該應(yīng)用程序能夠添加、列舉以及刪除任何給定的筆記:
#include "vely.h"
#include "login.h"
// 多租戶云中的筆記應(yīng)用程序
void notes () {
// 獲取全局請(qǐng)求數(shù)據(jù)
reqdata *rd;
get-req data to rd
// 如果會(huì)話有效,顯示登錄或注冊(cè)
if (!rd->is_logged_in) {
login_or_signup();
}
// 問(wèn)候用戶
@<h1>Welcome to Notes!</h1><hr/>
// 如果沒(méi)有登出,退出 —— 這里確保對(duì)用戶身份的安全驗(yàn)證
if (!rd->is_logged_in) {
exit-request
}
// 獲取 URL 參數(shù),告訴筆記要做什么
input-param subreq
// 顯示筆記能夠做什么操作(添加或列舉筆記)
@<a >Add Note</a> <a >List Notes</a><hr/>
// 列舉該用戶的所有筆記
if (!strcmp (subreq, "list")) {
// **只**選取該用戶的筆記
run-query @db_multitenant_SaaS = "select dateOf, note, noteId from notes where userId='%s' order by dateOf desc" : rd->sess_userId output dateOf, note, noteId
query-result dateOf to define dateOf
query-result note to define note
query-result noteId to define noteId
// 使用快速緩存正則表達(dá)式將新行更改為<br/>
match-regex "\n" in note replace-with "<br/>\n" result define with_breaks status define st cache
if (st == 0) with_breaks = note; // 什么都沒(méi)有發(fā)現(xiàn)/替換,只用原來(lái)的
// 顯示筆記
@Date: <<p-out dateOf>> (<a >delete note</a>)<br/>
@Note: <<p-out with_breaks>><br/>
@<hr/>
end-query
}
// 要求刪除筆記
else if (!strcmp (subreq, "delete_note_ask")) {
input-param note_id
@Are you sure you want to delete a note? Use Back button to go back, or <a >delete note now</a>.
}
// 刪除筆記
else if (!strcmp (subreq, "delete_note")) {
input-param note_id
// 刪除筆記
run-query @db_multitenant_SaaS = "delete from notes where noteId='%s' and userId='%s'" : note_id, rd->sess_userId affected-rows define arows no-loop error define errnote
// 告知用戶狀態(tài)
if (arows == 1) {
@Note deleted
} else {
@Could not delete note (<<p-out errnote>>)
}
}
// 添加筆記
else if (!strcmp (subreq, "add_note")) {
// 從 note 表單中獲取 URL POST 數(shù)據(jù)
input-param note
// 在該用戶的 ID 下插入筆記
run-query @db_multitenant_SaaS = "insert into notes (dateOf, userId, note) values (now(), '%s', '%s')" : rd->sess_userId, note affected-rows define arows no-loop error define errnote
// 告知用戶狀態(tài)
if (arows == 1) {
@Note added
} else {
@Could not add note (<<p-out errnote>>)
}
}
// 顯示一個(gè) HTML 表單來(lái)收集筆記,并將其發(fā)送回這里(使用 subreq="add_note" URL 參數(shù))
else if (!strcmp (subreq, "add")) {
@Add New Note
@<form action="https://opensource.com/?req=notes" method="POST">
@<input name="subreq" type="hidden" value="add_note">
@<textarea name="note" rows="5" cols="50" required autofocus placeholder="Enter Note"></textarea>
@<button type="submit">Create</button>
@</form>
}
}
具有 C 性能的 SaaS
Vely 語(yǔ)言使得 C 語(yǔ)言在你的網(wǎng)絡(luò)應(yīng)用程序中得到充分利用這件事成為可能。多租戶 SaaS 應(yīng)用程序便是從中受益的一個(gè)典型用例。
看一看參考代碼示例,寫(xiě)一寫(xiě)代碼,然后試試 Vely。