Paozhu ships with first-class support for multi-tenant SaaS. The underlying mechanism is flexible enough to cover several deployment styles:
siteid.aaa.com and bbb.com.And it can be arranged to feel like a microservice split: create a subdirectory under controller/src whose name looks like a domain (contains a dot), and every annotated function inside that directory becomes scoped to that tenant.
For example, create controller/src/aaa.com/ and controller/src/bbb.com/, each with its own news handler that behaves differently:
http://www.aaa.com/news → the handler registered in aaa.com/http://www.bbb.com/news → the handler registered in bbb.com/All of this runs inside a single monolith — a pragmatic middle ground for large multi-tenant projects.
Configure the domain section in conf/server.conf. Here is a sample for cn.aaa.com:
[cn.aaa.com]
wwwpath=/www/user/www/aaa
http2_enable=1
upload_max_size=16777216
siteid=9
groupid=0
alias_domain=aaa.com
themes=cn
Field-by-field:
wwwpath — the document root for cn.aaa.com. You can create this manually or let the super-admin backend create it (see the superadmin table in the cppcms database; the basesitepath column gives the base directory for all tenants under that super admin).http2_enable=1 — enable HTTP/2.upload_max_size=16777216 — max upload size, here 16 MB.siteid=9 — the tenant id, created by the super admin. Stored in the siteinfo table under userid.groupid=0 — reserved field for future tenant grouping.alias_domain=aaa.com — optional. When set, the framework looks for handler functions under controller/src/aaa.com/ first, before falling back to the default handlers. Any other domain sharing the same alias_domain will reuse that directory.themes=cn — prepends view/cn/ to view paths.themes_url= — public URL prefix for theme assets (images, CSS, etc.).The tenant-aware APIs live on httppeer:
std::string get_sitepath();
unsigned long long get_siteid();
unsigned long long get_groupid();
std::string get_theme();
std::string get_themeurl();
void theme_view(const std::string &view_path);
Typical layout with per-tenant controllers:
controller
├── src
│ └── aaa.com
│ └── article.cpp
Inside article.cpp:
namespace http
{
namespace aaa
{
//@urlpath(null, articles)
std::string front_article(std::shared_ptr<httppeer> peer)
{
unsigned int userid = peer->get_siteid();
peer->theme_view("front/articlelist");
return "";
}
} // namespace aaa
} // namespace http
Two things to notice:
aaa is derived from the directory name aaa.com — take the part before the dot. This keeps handlers isolated from one tenant to another.peer->theme_view("front/articlelist") automatically resolves to view/cn/front/articlelist.html because we configured themes=cn above. No hard-coded tenant paths in your controllers.Key benefits:
controller/src are lazy-loaded by the framework and only become active when alias_domain is set — so a single codebase can serve many tenants without cross-contamination.theme_view(...) lets admins switch themes per tenant from the backend (by changing themes=cn to e.g. themes=dark) without touching controller code.