❏ 站外平台:

利用 Vely 在 Linux 构建你自己的 SaaS

作者: Sergio Mijatovic 译者: LCTT Xiaoyan Zhang

| 2023-12-12 11:09      

Vely 可让你在网络应用程序中利用 C 语言的强大功能。

Vely 将 C 语言的高性能和低内存占用与 PHP 等语言的易用性和安全性相结合。作为自由开源软件,它以 GPLv3 和 LGPL 3 授权,所以你甚至可以用它来构建商业软件。

利用 Vely 构建 SaaS

你可以使用 Vely 创建一个多租户网络应用程序,它可以作为软件即服务模式(SaaS)在互联网上运行。每个用户都有一个完全独立的数据空间。

在这个网络应用程序示例中,用户可以注册一个笔记本服务来创建笔记,然后查看和删除它们。它仅用了 7 个源文件,310 行代码,就展示了如何集成多项技术:

  • MariaDB
  • 网络浏览器
  • Apache
  • Unix 套接字

运作原理

以下是从用户的角度来看应用程序是如何工作的。下图是代码演示。

该应用允许用户通过指定电子邮件地址和密码创建新的登录名。你可以用任何你喜欢的方式设置它们,例如运用 CSS:

创建一个用户账户

创建一个用户账户

验证用户的电子邮件:

验证用户的电子邮件地址

验证用户的电子邮件地址

每个用户使用自己独有的用户名和密码登录:

用户登录

用户登录

一旦登录,用户就可以添加笔记:

用户可以添加笔记

用户可以添加笔记

用户可以获取笔记列表:

用户列举笔记

用户列举笔记

删除笔记之前,应用会申请确认信息:

删除笔记之前,应用会申请确认信息

删除笔记之前,应用会申请确认信息

用户确认后,笔记被删除:

用户确认后,笔记被删除

用户确认后,笔记被删除

设置先决条件

遵照 Vely.dev 上的安装指示。这是一个使用 DNF、APT、Pacman 或者 Zypper 等标准工具包的快速流程。

由于它们都是这个范例的一部分,你必须安装 Apache 作为网络服务器,安装 MariaDB 作为数据库。

安装 Vely 后,如使用 Vim,打开里面的“语法高亮显示”:

  1. vv -m

获取源代码

这个演示 SaaS 应用程序的源代码是 Vely 安装的一部分。为每个应用程序创建一个单独的源代码目录不失为一个好主意(而且你可以按自己喜好命名)。在这种情况下,解包源代码会帮你完成这些工作:

  1. $ tar xvf $(vv -o)/examples/multitenant_SaaS.tar.gz
  2. $ cd multitenant_SaaS

默认情况下,该应用程序以 multitenant_SaaS 命名,但你可以将其命名为任何内容(如果这么做,其他每个地方你都需要改一下)。

创建应用程序

第一步是创建一个应用程序。使用 Vely 的 vf 工具就可以轻松完成:

  1. $ sudo vf -i-u $(whoami) multitenant_SaaS

这个命令创建了一个新的应用程序主目录(/var/lib/vv/multitenant_SaaS),并帮你执行应用程序设置。通常,这意味着在该主目录中创建各种子目录并分配权限。在这种情况下,只有当前用户(whoami 的结果)拥有目录,具有 0700 权限,这确保了其他人没有访问文件的权限。

创建数据库

在你键入任何代码之前,你需要一个能够存储该应用程序所用信息的空间。首先,创建一个名为 db_multitenant_SaaS 的 MariaDB 数据库,由用户名为 vely 的用户所有,密码为 your_password 。你可以修改刚才提到的任何值,但得记住,在这个示例里,你需要将包含这些内容的每个地方都得修改一遍。

在 MySQL 中以 root 身份登录:

  1. create database if not exists db_multitenant_SaaS;
  2. create user if not exists vely identified by 'your_password';
  3. grant create,alter,drop,select,insert,delete,update on db_multitenant_SaaS.* to vely;

然后在数据库内创建数据库对象(表,记录等等):

  1. use db_multitenant_SaaS;
  2. source setup.sql;
  3. exit

将 Vely 连接至数据库

为了让 Vely 知晓你数据库的位置以及如何登录进去,创建一个名为 db_multitenant_SaaS 的数据库配置文件。(该名称用于在源代码中的数据库声明,所以如果你改了它,确保在它存在的每个地方都改一遍。)

Vely 使用原生的 MariaDB 数据库连接,因此你可以指定给定的数据库所能允许的任何选项:

  1. $ echo '[client]
  2. user=vely
  3. password=your_password
  4. database=db_multitenant_SaaS
  5. protocol=TCP
  6. host=127.0.0.1
  7. port=3306' > db_multitenant_SaaS

构建应用程序

使用 vv 工具构建应用程序,利用 --db 选项指定 MariaDB 数据库和数据库配置文件:

  1. $ vv -q--db=mariadb:db_multitenant_SaaS

启用应用服务器

启动你的网络应用程序的服务器,需要使用 vf FastCGI 进程管理器。应用程序服务器使用 Unix 套接字与网络服务器(创建反向代理)通信:

  1. $ vf -w3 multitenant_SaaS

这么做会启用三个守护进程,为接收到的请求提供服务。你也可以启动一个自适应服务器,它会增加进程的数量从而服务更多的请求,并在不需要他们时减少进程的数量:

  1. $ vf multitenant_SaaS

请参阅 vf 了解更多选项,以帮助你实现最佳性能。

当你需要停止你的应用程序服务器,使用 -m quit 选项:

  1. $ vf -m quit multitenant_SaaS

创建网络服务器

这是一个网络应用程序,那么应用程序就得需要一个网络服务器。该示例通过一个 Unix 套接字监听器使用 Apache。

1、设置 Apache

将 Apache 配置为一个反向代理,并将你的应用程序与之连接,你需要启用 FastCGI 代理支持,这通常使用 proxy 和 proxy_fcgi 模块。

对于 Fedora 系统(或者其它的,比如 Arch)来说,通过在 Apache 配置文件 /etc/httpd/conf/httpd.conf 中添加(或取消注释)适当的 LoadModule 指令,就可启用 proxy 和 proxy_fcgi 模块。

以下指令适用于 Debian,Ubuntu 以及类似的系统,启用 proxy 和 proxy_fcgi 模块:

  1. $ sudo a2enmod proxy
  2. $ sudo a2enmod proxy_fcgi

以下指令适用于 OpenSUSE,将这几行添加在 /etc/apache2/httpd.conf 结尾处:

  1. LoadModule proxy_module modules/mod_proxy.so
  2. LoadModule proxy_fcgi_module modules/mod_proxy_fcgi.so

2、配置 Apache

现在你必须将代理信息添加在 Apache 的配置文件中:

  1. ProxyPass "/multitenant_SaaS" unix:///var/lib/vv/multitenant_SaaS/sock/sock|fcgi://localhost/multitenant_SaaS

你的配置文件的位置可能会有所不同,这取决于不同的 Linux 发行版:

  • Fedora、CentOS、Mageia 和 Arch: /etc/httpd/conf/httpd.conf
  • Debian、Ubuntu、Mint: /etc/apache2/apache2.conf
  • OpenSUSE:/etc/apache2/httpd.conf

3、重新启动

最后,重启 Apache。在 Fedora 和类似系统,还有 Arch Linux 是如下指令:

  1. $ sudo systemctl restart httpd

在 Debian 和基于 Debian 的系统,还有 OpenSUSE 是如下指令:

  1. $ sudo systemctl restart apache2

设置本地邮箱

这个示例中,电子邮件是其功能的一部分。如果你的服务器已经可以发送电子邮件了,你可以跳过这一条。此外,你可以使用本地邮箱(myuser@localhost)来测试它。要做到这一点,需安装 Sendmail。

在 Fedora 和类似系统中是如下指令:

  1. $ sudo dnf installsendmail
  2. $ sudo systemctl start sendmail

而在 Debian 和类似系统(如 Ubuntu):

  1. $ sudo apt installsendmail
  2. $ sudo systemctl start sendmail

当应用程序向本地用户发送电子邮件,比如说 OS_user@localhost,你就可以通过查看 /var/mail/ 处(即所谓“邮件池”)来确认电子邮件是否被发送。

从浏览器访问应用服务器

假设你在本地运行该应用,可以通过使用 http://127.0.0.1/multitenant_SaaS?req=notes&action=begin 域名从你的网络服务器访问你的应用服务器。如果你在互联网上的在线服务器运行该程序,你可能就需要调整防火墙设置以允许 HTTP 通信。

源代码

该应用程序示例包含 7 个源文件。你可以自行回顾代码(记住,这些文件只有 310 行代码),下面是每个文件的概述。

SQL 设置(setup.sql)

创建的两个表:

  • users:每个用户的信息。在 users 表中,每个用户都有自己唯一的 ID (userId 列),以及其他信息,如电子邮件地址和该地址是否通过了验证。还有一个哈希密码。实际的密码永远不会存储在纯文本(或其他形式)中,单向哈希用于检查密码。
  • notes:用户输入的笔记。notes 表包含了所有的笔记,每个笔记都有一个 userId 列,表示哪个用户拥有它们。userId 列的值与 users 表中的同名列匹配。这样,每个笔记显然都属于单个用户。

该文件内容如下:

  1. create table if not exists notes (dateOf datetime, noteId bigint auto_increment primary key, userId bigint, note varchar(1000));
  2. 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));
  3. create unique index if not exists users1 on users (email);

运行时数据(login.h)

为了正确地显示登录、注册和注销链接,你需要一些在应用程序中任何地方都可以使用的标志。此外,应用程序使用 cookie 来维护会话,因此它需要在任何地方都可用,例如,验证会话是否有效。发送到应用程序的每个请求都以这种方式进行确认。只有带有可验证 cookie 的请求是允许的。

所以要做到这种效果,你需要有一个 global_request_data 类型的 reqdata(请求数据),其中包含 sess_userId(用户的 ID)以及 sess_id(用户目前的会话 ID)。此外,还有一些不言自明的标志,可以帮助渲染页面:

  1. #ifndef _VV_LOGIN
  2. #define _VV_LOGIN
  3. typedef struct s_reqdata {
  4. bool displayed_logout; // true 则显示登出连接
  5. bool is_logged_in; // true 则会话已验证登录
  6. char *sess_userId; // 目前会话的用户 ID
  7. char *sess_id; // 会话 ID
  8. } reqdata;
  9. void login_or_signup ();
  10. #endif

会话检查和会话数据(_before.vely)

Vely 里有一个 请求前处理程序before_request handler 的概念。你写的代码会在其它处理请求的代码之前执行的。要达到这个目的,你只需要将这样的代码写在名为 _before.vely 的文件中,然后剩余的部分将会自动处理。

SaaS 应用程序所作的任何事情,例如处理发送至应用程序的请求,必须验证其安全性。这样,应用程序就能知晓调用方是否有执行操作所需要的权限。

在这里,通过请求前处理程序进行权限检查。这样,无论其他代码如何处理请求,都已经掌握了会话信息。

为保持会话数据(比如会话 ID 和用户 ID)在你代码中的任何地方都可用,你可以使用 global_request_data。它只是一个指向内存的通用指针(void*),任何处理请求的代码都可以访问它。这非常适合处理会话,如下所示:

  1. #include "vely.h"
  2. #include "login.h"
  3. // _before() 是一个请求前处理程序。
  4. // 它总是在处理请求的其他代码之前执行。
  5. // 对于任何类型的请求范围设置或数据初始化,它都是一个很好的位置。
  6. void _before() {
  7. // 输出 HTTP 请求头
  8. out-header default
  9. reqdata *rd; // 这是全局请求数据,见 login.h
  10. // 为全局请求数据分配内存,
  11. // 将在请求结束时自动释放
  12. new-mem rd size sizeof(reqdata)
  13. // 初始化标志
  14. rd->displayed_logout = false;
  15. rd->is_logged_in = false;
  16. // 将我们创建的数据设置为全局请求数据,
  17. // 可以从任何处理请求的代码中访问
  18. set-req data rd
  19. // 检查会话是否存在(基于来自客户端的 cookie)
  20. // 这在任何其他请求处理代码之前执行,
  21. // 使其更容易准备好会话信息
  22. _check_session ();
  23. }

检查会话是否有效(_check_session.vely)

多租户 SaaS 应用程序中最重要的任务之一就是通过检查用户是否登录来(尽快)检查会话是否有效。这是通过从客户端(例如网络浏览器)获取会话 ID 和用户 ID 的 cookie,并将它们与存储会话的数据库进行比较来实现的:

  1. #include "vely.h"
  2. #include "login.h"
  3. // 检查会话是否有效
  4. void _check_session () {
  5. // 获取全局请求数据
  6. reqdata *rd;
  7. get-req data to rd
  8. // 自用户浏览器获取 cookies
  9. get-cookie rd->sess_userId="sess_userId"
  10. get-cookie rd->sess_id="sess_id"
  11. if (rd->sess_id[0] != 0) {
  12. // 检查给定用户 ID 下的会话 ID 是否正确
  13. char *email;
  14. 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
  15. query-result email to email
  16. end-query
  17. if (rcount == 1) {
  18. // 如果正确,设置登录标志
  19. rd->is_logged_in = true;
  20. // 如果登出链接不显示,则显示它
  21. if (rd->displayed_logout == false) {
  22. @Hi <<p-out email>>! <a href="https://opensource.com/?req=login&action=logout">Logout</a><br/>
  23. rd->displayed_logout = true;
  24. }
  25. } else rd->is_logged_in = false;
  26. }
  27. }

注册、登录、登出(login.vely)

任何多租户系统的基础便是具有用户注册\登录和登出的功能。通常情况下,注册包括验证电子邮件地址;不止于此,同一电子邮件地址会作为一个用户名。这里就是这种情况。

这里实现了几个执行该功能所必须的子请求:

  • 注册新用户时,显示 HTML 表单以收集信息。它的 URL 请求签名是 req=login&action=newuser
  • 作为对注册表单的响应,创建一个新用户。URL 请求的签名是 req=login&action=createuser。输入参数(input-param)信号获取 email 和 pwd 的 POST 表单字段。密码值是单向散列,电子邮件验证令牌是一个随机的 5 位数字。这些被插入到 users 表中,创建一个新用户。系统会发送一封验证邮件,并提示用户阅读邮件并输入代码。
  • 通过输入发送到该电子邮件的验证码来验证电子邮件。URL 请求的签名是 req=login&action=verify
  • 显示一个登录表单,让用户登录。URL 请求的签名是 req=login(例如,action 为空)。
  • 通过验证电子邮件地址(用户名)和密码登录。URL 请求的签名是 req=login&action=login
  • 应用户要求登出。URL 请求的签名是 req=login&action=logout
  • 应用程序的登录页。URL 请求的签名是 req=login&action=begin
  • 如果用户当前已登录,转到应用程序的登录页面。

可以看看下面这些例子:

  1. #include "vely.h"
  2. #include "login.h"
  3. // 处理云端多租户应用程序的会话维护、登录、注销、会话验证
  4. void login () {
  5. // 获取 URL 的输入参数 `action`
  6. input-param action
  7. // 获取全局请求数据,我们在其中记录会话信息,所以它很方便
  8. reqdata *rd;
  9. get-req data to rd
  10. // 如果会话已经建立,我们不会
  11. // 继续到应用程序主页的唯一原因是我们正在登出
  12. if (rd->is_logged_in) {
  13. if (strcmp(action, "logout")) {
  14. _show_home();
  15. exit-request
  16. }
  17. }
  18. // 应用程序页面启动。显示登录或注册的链接,
  19. // 并显示适当的主屏幕
  20. if (!strcmp (action, "begin")) {
  21. _show_home();
  22. exit-request
  23. // 开始创建新用户。询问电子邮件和密码,
  24. // 然后提交此表单时创建用户。
  25. } else if (!strcmp (action, "newuser")) {
  26. @Create New User<hr/>
  27. @<form action="https://opensource.com/?req=login" method="POST">
  28. @<input name="action" type="hidden" value="createuser">
  29. @<input name="email" type="text" value="" size="50" maxlength="50" required autofocus placeholder="Email">
  30. @<input name="pwd" type="password" value="" size="50" maxlength="50" required placeholder="Password">
  31. @<input type="submit" value="Sign Up">
  32. @</form>
  33. // 验证用户发送到电子邮件的代码。代码必须匹配,从而验证电子邮件地址
  34. } else if (!strcmp (action, "verify")) {
  35. input-param code
  36. input-param email
  37. // 获取基于电子邮件的验证令牌
  38. run-query @db_multitenant_SaaS = "select verify_token from users where email='%s'" output db_verify : email
  39. query-result db_verify to define db_verify
  40. // 将数据库中记录的令牌与用户提供的令牌进行比较
  41. if (!strcmp (code, db_verify)) {
  42. @Your email has been verifed. Please <a href="https://opensource.com/?req=login">Login</a>.
  43. // 如果匹配,更新用户信息以表明已验证。
  44. run-query @db_multitenant_SaaS no-loop = "update users set verified=1 where email='%s'" : email
  45. exit-request
  46. }
  47. end-query
  48. @Could not verify the code. Please try <a href="https://opensource.com/?req=login">again</a>.
  49. exit-request
  50. // 创建用户 —— 当用户使用电子邮件和密码提交表单以创建用户时运行
  51. } else if (!strcmp (action, "createuser")) {
  52. input-param email
  53. input-param pwd
  54. // 创建散列(单向)密码
  55. hash-string pwd to define hashed_pwd
  56. // 生成随机的 5 位数字字符串验证代码
  57. random-string to define verify length 5 number
  58. // 创建用户:插入电子邮件、哈希密码、验证令牌。当前验证状态为 0,或未验证
  59. begin-transaction @db_multitenant_SaaS
  60. 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
  61. if (strcmp (err, "0") || arows != 1) {
  62. // 如果不能添加用户,则可能该用户不存在。不管怎样,我们都无法继续。
  63. login_or_signup();
  64. @User with this email already exists.
  65. rollback-transaction @db_multitenant_SaaS
  66. } else {
  67. // 创建带有验证码的电子邮件并将其发送给用户
  68. write-string define msg
  69. @From: vely@vely.dev
  70. @To: <<p-out email>>
  71. @Subject: verify your account
  72. @
  73. @Your verification code is: <<p-out verify>>
  74. end-write-string
  75. exec-program "/usr/sbin/sendmail" args "-i", "-t" input msg status define st
  76. if (st != 0) {
  77. @Could not send email to <<p-out email>>, code is <<p-out verify>>
  78. rollback-transaction @db_multitenant_SaaS
  79. exit-request
  80. }
  81. commit-transaction @db_multitenant_SaaS
  82. // 通知用户查看邮件并输入验证码
  83. @Please check your email and enter verification code here:
  84. @<form action="https://opensource.com/?req=login" method="POST">
  85. @<input name="action" type="hidden" value="verify" size="50" maxlength="50">
  86. @<input name="email" type="hidden" value="<<p-out email>>">
  87. @<input name="code" type="text" value="" size="50" maxlength="50" required autofocus placeholder="Verification code">
  88. @<button type="submit">Verify</button>
  89. @</form>
  90. }
  91. // 这里在登录用户登出时运行
  92. } else if (!strcmp (action, "logout")) {
  93. // 更新用户表以清除会话,即没有该用户登录
  94. if (rd->is_logged_in) {
  95. run-query @db_multitenant_SaaS = "update users set session='' where userId='%s'" : rd->sess_userId no-loop affected-rows define arows
  96. if (arows == 1) {
  97. rd->is_logged_in = false; // 提示用户未登录
  98. @You have been logged out.<hr/>
  99. }
  100. }
  101. _show_home();
  102. // 登录:当用户输入用户名和密码时运行
  103. } else if (!strcmp (action, "login")) {
  104. input-param pwd
  105. input-param email
  106. // 创建单向散列,目的是与用户表进行比较 —— 密码**永远不会**被记录
  107. hash-string pwd to define hashed_pwd
  108. // 为会话 ID 创建一个随机的 30 位长的字符串
  109. random-string to rd->sess_id length 30
  110. // 检查用户名和哈希密码是否匹配
  111. run-query @db_multitenant_SaaS = "select userId from users where email='%s' and hashed_pwd='%s'" output sess_userId : email, hashed_pwd
  112. query-result sess_userId to rd->sess_userId
  113. // 如果匹配,使用会话 ID 更新用户表
  114. 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
  115. if (arows != 1) {
  116. @Could not create a session. Please try again. <<.login_or_signup();>> <hr/>
  117. exit-request
  118. }
  119. // 设置“用户 ID”和“会话 ID”为 cookie。用户的浏览器将在每个请求中返回这些信息
  120. set-cookie "sess_userId" = rd->sess_userId
  121. set-cookie "sess_id" = rd->sess_id
  122. // 显示主页,确保会话是正确的,并设置标志
  123. _check_session();
  124. _show_home();
  125. exit-request
  126. end-query
  127. @Email or password are not correct. <<.login_or_signup();>><hr/>
  128. // 登录界面,要求用户输入用户名和密码
  129. } else if (!strcmp (action, "")) {
  130. login_or_signup();
  131. @Please Login:<hr/>
  132. @<form action="https://opensource.com/?req=login" method="POST">
  133. @<input name="action" type="hidden" value="login" size="50" maxlength="50">
  134. @<input name="email" type="text" value="" size="50" maxlength="50" required autofocus placeholder="Email">
  135. @<input name="pwd" type="password" value="" size="50" maxlength="50" required placeholder="Password">
  136. @<button type="submit">Go</button>
  137. @</form>
  138. }
  139. }
  140. // 显示登录或注册链接
  141. void login_or_signup() {
  142. @<a href="https://opensource.com/?req=login">Login</a> & & <a href="https://opensource.com/?req=login&action=newuser">Sign Up</a><hr/>
  143. }

通用应用程序(_show_home.vely)

借助本教程,你可以创建你想要的任何多租户 SaaS 应用程序。上面的多租户处理模块(login.vely)调用 _show_home() 函数,它可以容纳你的任何代码。这个示例代码展示了笔记应用程序,但它可以是任何内容。_show_home() 函数可以调用你想要的任何代码,它是一个通用的多租户应用程序插件:

  1. #include "vely.h"
  2. void _show_home() {
  3. notes();
  4. exit-request
  5. }

笔记应用程序(notes.vely)

该应用程序能够添加、列举以及删除任何给定的笔记:

  1. #include "vely.h"
  2. #include "login.h"
  3. // 多租户云中的笔记应用程序
  4. void notes () {
  5. // 获取全局请求数据
  6. reqdata *rd;
  7. get-req data to rd
  8. // 如果会话有效,显示登录或注册
  9. if (!rd->is_logged_in) {
  10. login_or_signup();
  11. }
  12. // 问候用户
  13. @<h1>Welcome to Notes!</h1><hr/>
  14. // 如果没有登出,退出 —— 这里确保对用户身份的安全验证
  15. if (!rd->is_logged_in) {
  16. exit-request
  17. }
  18. // 获取 URL 参数,告诉笔记要做什么
  19. input-param subreq
  20. // 显示笔记能够做什么操作(添加或列举笔记)
  21. @<a href="https://opensource.com/?req=notes&subreq=add">Add Note</a> <a href="https://opensource.com/?req=notes&subreq=list">List Notes</a><hr/>
  22. // 列举该用户的所有笔记
  23. if (!strcmp (subreq, "list")) {
  24. // **只**选取该用户的笔记
  25. 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
  26. query-result dateOf to define dateOf
  27. query-result note to define note
  28. query-result noteId to define noteId
  29. // 使用快速缓存正则表达式将新行更改为<br/>
  30. match-regex "\n" in note replace-with "<br/>\n" result define with_breaks status define st cache
  31. if (st == 0) with_breaks = note; // 什么都没有发现/替换,只用原来的
  32. // 显示笔记
  33. @Date: <<p-out dateOf>> (<a href="https://opensource.com/?req=notes&subreq=delete_note_ask&note_id=%3C%3Cp-out%20noteId%3E%3E">delete note</a>)<br/>
  34. @Note: <<p-out with_breaks>><br/>
  35. @<hr/>
  36. end-query
  37. }
  38. // 要求删除笔记
  39. else if (!strcmp (subreq, "delete_note_ask")) {
  40. input-param note_id
  41. @Are you sure you want to delete a note? Use Back button to go back, or <a href="https://opensource.com/?req=notes&subreq=delete_note&note_id=%3C%3Cp-out%20note_id%3E%3E">delete note now</a>.
  42. }
  43. // 删除笔记
  44. else if (!strcmp (subreq, "delete_note")) {
  45. input-param note_id
  46. // 删除笔记
  47. 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
  48. // 告知用户状态
  49. if (arows == 1) {
  50. @Note deleted
  51. } else {
  52. @Could not delete note (<<p-out errnote>>)
  53. }
  54. }
  55. // 添加笔记
  56. else if (!strcmp (subreq, "add_note")) {
  57. // 从 note 表单中获取 URL POST 数据
  58. input-param note
  59. // 在该用户的 ID 下插入笔记
  60. 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
  61. // 告知用户状态
  62. if (arows == 1) {
  63. @Note added
  64. } else {
  65. @Could not add note (<<p-out errnote>>)
  66. }
  67. }
  68. // 显示一个 HTML 表单来收集笔记,并将其发送回这里(使用 subreq="add_note" URL 参数)
  69. else if (!strcmp (subreq, "add")) {
  70. @Add New Note
  71. @<form action="https://opensource.com/?req=notes" method="POST">
  72. @<input name="subreq" type="hidden" value="add_note">
  73. @<textarea name="note" rows="5" cols="50" required autofocus placeholder="Enter Note"></textarea>
  74. @<button type="submit">Create</button>
  75. @</form>
  76. }
  77. }

具有 C 性能的 SaaS

Vely 语言使得 C 语言在你的网络应用程序中得到充分利用这件事成为可能。多租户 SaaS 应用程序便是从中受益的一个典型用例。

看一看参考代码示例,写一写代码,然后试试 Vely。

(题图:DA/126624c8-1a47-481b-b149-92273e8e0f4f)


via: https://opensource.com/article/22/11/build-your-own-saas-vely

作者:Sergio Mijatovic 选题:lkxed 译者:Drwhooooo 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出



最新评论

从 2025.1.15 起,不再提供评论功能
LCTT 译者
Xiaoyan Zhang 🌟🌟
共计翻译: 5.0 篇 | 共计贡献: 333
贡献时间:2023-01-10 -> 2023-12-09
访问我的 LCTT 主页 | 在 GitHub 上关注我


返回顶部

分享到微信

打开微信,点击顶部的“╋”,
使用“扫一扫”将网页分享至微信。