Bearweb架构

Bearweb CMS是一个数据库驱动的架构。本文将展示其架构于核心模组:站点地图、会话控制、用户管理。

--by @ Feb 2, 2026

Index

代码风格

文件bearweb.class.php包含进入点和3个模组,包含Bearweb CMS进入点class _Bearweb、站点地图模组class _Bearweb_Site、会话模组class _Bearweb_Session、用户模组class _Bearweb_User。不要修改bearweb.class.php

index.php里,你需要扩展进入点为Bearweb类,以及3个模组为Bearweb_SiteBearweb_SessionBearweb_User。你可以在扩展后的类中重载类方程和变量。

因为Bearweb CMS是数据库驱动的,你将需要为每一个模组指定其数据库。

下面的例子展示了index.php中扩展会话模组,重载会话令牌的cookie名和有效期,指定会话数据库:


class Bearweb_Session extends _Bearweb_Session {
	const CookieSID = 'SessionID';
	const CookieKey = 'SessionKey';
	const Expire = 7 * 24 * 3600;

	public static function init(): void {
		try { static::$db = new PDO('sqlite:./bw_session.db', null, null, [
			PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
			PDO::ATTR_TIMEOUT => 10, #10s waiting time should be far more than enough
			PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
		]); } catch (Exception $e) { throw new BW_DatabaseServerError('Fail to open DB: '.$e->getMessage(), 500); }
	}
}
	

Bearweb CMS使用PHP前端于SQLite后端。Bearweb CMS使用intstringarray(包括数组和对象)数据类型,这些类型都被PHP支持,但是array作为例外不被SQLite支持。因此,一个PHP的array将会被转化为JSON并保存在string中,再被写入数据库。读出反之。

错误处理

Bearweb的模组和模板在执行期间可以抛出Bearweb Exception类BW_Error,并被Bearweb进入点类Bearweb捕捉。当错误发生(无论是服务器出错还是客户端甩锅),Bearweb进入点类会讲错误记录在Apache2日志中,并执行错误页面模板

The relationship of BW_Error class family is given below:

开发者

BW_Error(string $message, int $code) - 抛出错误

使用throw BW_Error(string $message, int $code)来终止当前执行的模组或模板,并让Bearweb CMS调用错误页面模板。其中:

$e->getMessage(): string - 获取错误信息

和PHP原生Exception类相似,使用$e->getMessage(): string来获取错误信息;或使用echo $e;来打印错误信息和其它相关信息,如呼叫栈。

举例:


try {
	...
	$db->execute();
} catch (Exception $e) {
	throw new BW_DatabaseServerError('Cannot insert into sitemap database: '.$e->getMessage(), 500);
}
...
echo $e->getMessage(); // echo 'BW_DatabaseServerError - Cannot insert into sitemap database: DBMS message'
	

进入点

当服务器收到一个HTTP请求,底层的Apache2将会执行包含Bearweb CMS的PHP脚本。接下来,Bearweb CMS进入点Bearweb会执行一下操作:

  1. 初始化3个核心模组:站点地图、会话控制、用户管理。因为Bearweb CMS是数据库驱动的,这一步包括连接到模组的数据库。
  2. 会话管理。Bearweb CMS将判断该请求所属的会话,或在请求不属于任何会话的情况下创建一个新的会话以便后续请求使用。
  3. 判断会话绑定的用户。这让Bearweb CMS可以判断请求所有的权限。
  4. 读取请求的资源(例如网页、图片)或执行请求的API,如果用户有相关的权限的话。
  5. 执行该资源或API的模板以进一步处理请求。

如果前3步出错,神仙也救不了,Bearweb CMS返回一个致命错误。

当第4、5步错误发生(包括服务端和客户端),Bearweb进入点类将会将错误信息写入Apache2日志并调用错误页模板

开发者

3个模组都以成员变量的形式包含在Bearweb CMS进入点内Bearweb


class Bearweb {
	protected Bearweb_Session $session;
	protected Bearweb_User $user;
	protected Bearweb_Site $site;
}
	

要访问一个模组实例,可以使用$this->module$BW->module。例如:


echo $this->session->sID; // echo session ID 'AZaz09'
echo $BW->site->url; // echo requested resource's URL 'hello.html'
$this->session->bindUser('John'); // bind current session to user 'John'
	

出于安全考虑,你可以重载Bearweb::HideServerErrortrue来隐藏服务端错误。这样,Bearweb CMS将发送一个普通的500 Internal Server Error的HTTP头,不会将实际的错误信息返回给客户端。无论Bearweb::HideServerError设置,实际错误都会被写入Apache2日志。.

会话(Session)控制

会话于交互

一个交互(Transaction)指由客户端发送给服务端的一次HTTP请求和服务端返回给客户端的相应回复。

对于Bearweb CMS来说,一个请求包含了3部分:

作为一个数据库驱动的架构,Bearweb CMS将每一次交互都记录在数据库中

对于Bearweb CMS来说,一个会话(Session)包含了来自同一个客户端的请求。

作为一个数据库驱动的架构,Bearweb CMS将每一个会话都记录在数据库中

会话令牌

Bearweb CMS使用客户端cookie和服务器数据库来保存会话令牌。令牌包括3部分:

会话钥匙对于客户端运行的JavaScript是可见的,可以拿来作哈希计算。一个可疑的JS可以窃取该信息(XSS攻击)来冒充一个会话。为了防止该问题,我们将会话ID设置为HTTP-only。客户端运行的代码将无法看到这个cookie,也就没办法偷走了。但是,这个cookie会被包含在所有发送给服务器的HTTP请求中,允许服务器读取它作为会话令牌验证。

在第一次交互中,客户端的cookie是空的,也就是说,该请求不包含会话令牌。服务器将会生成一个新的会话,将2个cookie发送到客户端,即:会话ID、会话钥匙。

在接下来的交互中,客户端的cookie包含了会话令牌。当客户端发送请求到服务器时,也就包含了会话令牌。服务器通过会话令牌判断客户所属会话。

一个可疑的请求可能包含了伪造的令牌。然而,想要盗取一个会话ID几乎是不可能的(因为是HTTP-only)。当收到的令牌的ID与钥匙不符时,Bearweb CMS将生成并发送一个新的令牌。

如果上次使用时间戳太久了,该会话即过期。将使用一个新的会话。

记录交互与会话控制

当Bearweb CMS收到一个请求时将创建一个Bearweb_Session实例。根据请求提供的会话令牌,Bearweb将执行其一:

Bearweb CMS也会交互数据库中记录:

以上两项任务将需要写会话数据库与交互数据库,且必须成功。如果任意任务失败,Bearweb CMS将回滚数据库写入信息,并终止执行。

当请求完成并返回结果给客户端后,Bearweb CMS将更新数据库中的交互信息:

该任务发生于执行结束前PHP引擎卸载Bearweb_Session实例时,这个任务(非常小概率)可能失败。该失败神仙也救不了,将被忽略,数据库修改将不会生效。

Bearweb_Session

一个Bearweb_Session类包含以下信息:

Bearweb_Session类变量

变量名 类型(PHP) 阐述 示例
sID public readonly string 会话ID。
  • 'zaZA90=+' - 使用这个ID来查找一个会话。
sCreate public readonly int 会话创建时间。
  • 60 - 会话创建于1970-01-01 00:01:00。该值为服务器接收到请求的实际时间,而非客户端发送请求的时间。
sLastUse public readonly int 会话上次使用时间。
  • 120 - 会话最后使用于1970-01-01 00:02:00。该值为服务器接收到请求的实际时间,而非客户端发送请求的时间。
sUser public readonly string 会话绑定用户ID。
  • '' - 游客会话。
  • 'John' - 绘画绑定为用户“John”。
sKey public readonly string 会话钥匙。
  • '90=+zaZA' - 会话钥匙。
tID public readonly string 交互ID。
  • 'AZaz09+=' - 使用这个ID来查找一个交互。
tCreate public readonly int 交互创建时间。
  • 60 - 交互创建于1970-01-01 00:01:00。该值为服务器接收到请求的实际时间,而非客户端发送请求的时间。
tIP public readonly string 交互客户端IP地址与端口,可以是IPv4或IPv6地址。
  • '1.2.3.4:10086' - 客户端IP地址与端口。
  • '1234:5678:90ab:cdef:ff00:5599:abcd:1234:12345' - 客户端IP地址与端口。
tURL public readonly string 交互请求的资源或API的URL。
  • 'hello/world.html' - 请求的资源或API的URL。
tSID public readonly string 交互对应会话ID。
  • 'zaZA90=+' - 该ID用于查找对应会话ID。
tLog public readonly string 交互日志。调试用。使用$BW->log('Hello world');来打一行log。
  • 'Hello\nStart\nDone!' - 一些log。
  • '' - 没有log。或者也可能,
  • '' - 致命错误发生。PHP脚本在写入log前就退出了。

log(string $log): string - 添加一行交互日志

使用$BW->session->log(string $log): string来添加一行交互日志,其中:


// Log is empty at first

echo $BW->session->log('A'); // echo "\nA"
$BW->session->log('B');
echo $BW->session->log('C'); // echo "\nA\nB\nC"

// At the end, "\nA\nB\nC" will be write to transaction database
	

日志将在前端缓存,只有在进程结束后再写入数据库。(Write-back策略)

bindUser(string $uid): void - 会话绑定用户

使用$BW->session->bindUser(string $uid): void来绑定会话的用户,其中:

这个方程将写入用户到数据库。使用该会话令牌的后续请求将使用该用户。


// Transaction at time 1:
echo $BW->session->user['ID']; // echo '', guest, current session has no user bind
$BW->session->bindUser('uid123');

// Transaction at time 2:
echo $BW->session->user['ID']; // echo 'uid123', current session bind to user with ID 'uid123'

// Transaction at time 3:
$BW->session->bindUser(''); //Logout, bind current session back to guest

// transaction at time 4:
echo $BW->session->user['ID']; // echo '', guest, no user bind
	

该方程可能抛出BW_DatabaseServerError

updateKey(): string - 更新服务端与客户端的会话钥匙

出于安全考虑,你也许想使用$BW->session->updateKey(): string来生成新的会话钥匙,其中:

这个方程将把新的钥匙写入数据库并发送给客户端。后续请求必须使用新的钥匙。

该方程可能抛出BW_DatabaseServerError

用户(User)管理

判断会话用户

如果一个会话绑定了用户,数据库中该会话将包含该用户的ID。该情况下,Bearweb CMS将查找用户数据库来决定该用户的组。同时,Bearweb还会更新该用户的最后活动时间。

如果会话未绑定用户,用户就是“游客”。

使用用户名和密码通过“登录”请求来绑定会话用户。如果登录成功,Bearweb CMS就会更新会话数据库中的会话用户。该会话的后续交互就会使用这个用户ID与组。

在架构层面,Bearweb CMS只关注请求的用户和组。用户和组决定了用户在访问某个资源时的权限。例如,一个资源只能被所有者和管理员组修改,一个启用访问控制的资源只能被白名单用户和组读取。

密码哈希

虽然使用HTTPS防止网络上第三方看到密码,但密码仍然以明文方式呈现给服务器。服务器端的可以程序将能窃取密码。例如,加载了一个有问题的PHP库。

在保存密码到数据库前使用PHP自带的password_hash()哈希可以防止其它进程通过读数据库窃取密码明文,但仍然无法杜绝服务端在哈希前的密码泄露。

在客户端就进行哈希能够防止密码明文暴露在服务端或在传输过程中。

加盐能够提高安全性。使用动态的盐锦上添花。

Bearweb CMS使用动态加盐来保护密码。

哈希算法为SHA-384,使用JavaScript的window.crypto.subtle.digest('SHA-384', 'password')方程。该方程需要HTTPS。

注册

使用会话钥匙(会话开始时随机生成)作为盐对用户密码的明文(用户键盘输入)加密。


hashedPassowrd = hash(sessionKey + rawPassword);
xhr_register(id, hashedPassowrd);
	

服务器保存用户数据时将保存会话钥匙为salt、哈希过后的密码为password

登录

在登录时,客户端首先需要下载上一次哈希时使用的盐来计算出服务器上保存的哈希过后的密码。但是,密码明文在该哈希过后的密码将不会被直接发送,而是通过当前会话钥匙被再次哈希后才发送给服务器。服务器通过数据库中上次哈希后的密码和当前会话钥匙哈希计算验证。

接下来,客户端使用用户输入的密码明文于当前会话钥匙哈希计算新的哈希过后的密码。服务器修改用户数据库使用新的哈希和密码。


oldSalt = xhr_getSalt(id);
oldHashedPassword = hash(oldSalt + rawPassword);
verifyHashedPassword = hash(sessionKey + oldHashedPassword);
newHashedPassword = hash(sessionKey + rawPassword);
xhr_login(id, verifyHashedPassword, newHashedPassword);
	

可以通过提供新的密码来计算newHashedPassword的方式修改密码。

Bearweb_User

一个Bearweb_User类包含以下信息:

Bearweb_User类变量

变量名 类型(PHP) 阐述 示例
id public string 用户ID。唯一的。区分大小写。
  • 'John' - 使用这个ID来查找一个用户。
name public string 用户昵称。这个昵称可以重名。
  • 'John Doe' - 用户的昵称。
salt public string 密码用盐。
  • 'AZaz09+=' - 密码哈希加盐。
password public string 加盐哈希后的32字节(256-bit)密码。
  • 'zaZA90=+' - 加盐哈希后的密码。
registertime public int 创建帐号时间戳。
  • 60 - 帐号创建于1970-01-01 00:01:00。该值为服务器接收到请求的实际时间,而非客户端发送请求的时间。
lastactive public int 最后活跃时间戳。
  • 120 - 帐号最后活跃于1970-01-01 00:02:00。该值为服务器接收到请求的实际时间,而非客户端发送请求的时间。
group public array 用户的分组。使用int作为组ID。组0为“管理员”。
  • '[0, 1]' - 用户在管理员分组和“分组1”。
  • '[114]' - 用户在“分组114”。
data public array 用户的其它数据。
  • '{"key1": "value 1", "key2": 114514}' - 用户数据。

Bearweb_User构造方法

参数 默认 可接受类型与例子
id string ''(空字符串) - 无用户名。
  • string 'John'
name string 'Guest' - 访客昵称/
  • 'John Doe' - 用户的昵称。
salt string ':' - 非法盐。你可以通过非法格式来禁用一个用户登录。
  • 'AZaz09+=' - 密码哈希加盐。
password string ':' - 非法密码。你可以通过非法格式来禁用一个用户登录。
  • 'zaZA90=+' - 加盐哈希后的密码。
registertime Bearweb_User::TIME_CURRENT - 使用当前时间戳。
  • int 60 - 帐号创建于1970-01-01 00:01:00。
  • Bearweb_User::TIME_CURRENT - 将转化为当前时间戳。
  • Bearweb_User::TIME_NULL - 无实际时间戳。
lastactive Bearweb_User::TIME_CURRENT - 使用当前时间戳。
  • int 120 - 帐号最后活跃于1970-01-01 00:02:00。
  • Bearweb_User::TIME_CURRENT - 将转化为当前时间戳。
  • Bearweb_User::TIME_NULL - 无实际时间戳。
group array [] - 无分组。
  • '[0, 1]' - 用户在管理员分组和“分组1”。
  • '[114]' - 用户在“分组114”。
  • '[114]' - 合法JSON字符串将被解码为[114]
  • '[114' - 非法JSON字符串将被解码为[]
data array [] - 无数据。
  • ['key1' => 'value 1', 'key2' => 114514] - 用户数据。
  • '{"key1": "value 1", "key2": 114514}' - 合法JSON字符串将被解码为['key1' => 'value 1', 'key2' => 114514]
  • '['key1": "value 1, "key2": 114514' - 非法JSON字符串将被解码为[]

isAdmin(): bool, isGuest(): bool - 用户是否为访客或管理员

要验证一个用户是否为访客或是否为管理员(组0),使用:


echo $BW->user->isGuest() ? 'Register first' : ('Hello old friend '.$BW->user->id);

if($someuser->isAdmin())
	show_system_load();
else
	throw new BW_ClientError('Access denied', 403);
	

管理员分组为0

用户名为''(空字符串)需要特殊考虑。

  • 如果指会话的用户为'',该会话为游客。
  • 如果一个资源的所有者为'',该资源所有者为系统。

validID(string $uid): bool, validPassword(string $pass): bool - 验证用户提交信息格式

要验证用户提交信息格式,使用:


if ( !Bearweb_User::validID($_POST['ID']) || !Bearweb_User::validPassword($_POST['password']) )
	throw new BW_ClientError('Bad data format', 400);
	

query(string $id, int $flag = 0): ?Bearweb_User - 查找用户

使用Bearweb_User::query(string $id, int $flag = 0): ?Bearweb_User来从数据库中查找一个用户,其中:

例如:


$user = Bearweb_User::query('John');
echo 'Hello',$user->nickname;
	

该方程可能抛出BW_DatabaseServerError

insert(): void, update(): void - 创建或修改用户

要创建或修改一个用户,首先创建一个Bearweb_User实例,并提供用户信息;或是查找一个用户,再修改用户数据。接下来,使用$user->insert()来创建用户,或是$user->update()来修改一个用户。例如:


$user = new Bearweb_User(
	id:		'John',
	registertime:	114514,
	lastactive:	1919810,
	... /* Other data */
);
$user->insert(); // 创建用户
$user->update(); // 直接覆盖一个用户(使用该ID)

$user = Bearweb_User::query('John');
$user->nickname = 'John Doe II';
$user->update(); // 修改一个用户的某项数据
	

该方程可能抛出BW_DatabaseServerError

站点地图(Sitemap)

网络服务器和文件服务器相似。客户端发送一个包含URL(文件名)、会话令牌(读写权限)、可选额外数据(上传文件)的请求到服务器以下载某个资源(文件内容)。

请求一个资源(例如网页、图片)说白了就是从服务器下载那个文件。

Bearweb CMS使用数据库来储存资源和相关元素据。每一个资源都可以通过URL进行索引。在数据发送给客户端前,元素据可以用于整合访问控制和数据处理。

数据库被称作站点地图

请求资源

当Bearweb CMS调用一个资源的模板前会:

  1. 如果该资源不存在,就返回一个HTTP 404 Not Found。终止执行并调用错误模板。
  2. 访问控制。如果资源定义了meta.access,只有白名单meta.access中的用户和组才能读取该资源。资源的所有者和管理员永远有读权限。如果用户没有访问权限,终止执行并调用错误模板。
  3. 重定向。如果资源定义了meta.r301meta.r302,就发送HTTP 301或302。终止执行并调用错误模板。
  4. 缓存控制。如果一个资源没有修改时间戳modify,该资源就是实时生成的。Bearweb CMS会发送一个随机E-Tag并禁用客户端缓存(比如说,API返回的结果应该表示最新消息)。否则,Bearweb CMS将允许客户端缓存以减少重复请求该资源(比如说,每一张网页都是用相同样式表,可以复用)。
  5. 按实际需求发送其它HTTP头。

接下来,根据资源的template执行模板,具体参考模板文档

三级站点地图

固定地图(Fixed map)

可以把一个资源直接写在代码里Bearweb_Site::FixedMap


const FixedMap = [
	'favicon.ico' => ['category' => 'Web', 'create' => 1145141919, 'modify' => 1145141919, 'content' => null, 'aux' => ['mime' => 'image/x-icon']],
	'api/dryrun' => ['category' => 'API', 'template' => ['api','general'], 'meta' => ['task' => 'nop']],
	...
];
	

当Bearweb CMS超找一个资源是,会先通过URL作为键查看固定地图。正如其名曰,固定地图保存的资源都是只读的、固定的、永远不会改变的(存废修改代码)。这可以包括样式表、JavaScript文件、图标、API。

使用固定地图速度快且不会出错,因为该资源存在于代码中(程序内存),可以立刻使用。

创建或修改一个可以在固定地图中找到的资源会抛出BW_DatabaseServerError

数据库

如果在固定地图中找不到该资源,Bearweb CMS就会去找站点地图数据库。

数据库的资源时可被修改的。因此,适合用于保存在网站运行途中需要读写的、用户生成的内容(如文章、图片)。

不过,访问数据库需要读写文件,所以速度会慢一些,且有可能出现执行错误。

文件支持内容

Bearweb CMS不仅支持HTML网页,还支持文件,如图片。

文件可以很大。比如,一张照片可能会好几MB或是好几十MB。所有资源(包括图片等文件)加起来可能会上GBs,全都一股脑存入数据库会导致数据库臃肿低效,很不优雅。

要给数据库减负,我们在数据库中只保存元数据,而大文件的内容将使用外部文件保存。小文件的内容仍然保存在数据库中。

可以参考这一篇文章

Bearweb_Site

一个Bearweb_Site类包含以下信息:

Bearweb_Site类变量

变量名 类型(PHP) 阐述 示例
url public string 资源URL。唯一的。区分大小写。
  • 'hello/world.html' - 使用这个URL来访问该资源。
category public string 资源分类,用于管理目的,Bearweb CMS不使用这个值。
  • 'Article' - 这个资源属于一个“Article”。
template public array 进一步处理这个资源的模板。Bearweb CMS将调用template[0](模板),然后template[0]将调用template[1](子模板)。
  • '['page-en', 'direct']' - Bearweb CMS将调用Bearweb_Site::Dir_Template目录下的page-en.php, 然后page-en.php将调用Bearweb_Site::Dir_Template目录下的page-en_direct.php
owner public string 资源所有者的用户ID(user id)。只有所有者和管理员(组0)才可以修改这个资源。
  • '' - 系统所有资源。例如自动生成的资源(例如RSS站点地图)。
  • 'John' - 用户名为“John”的资源。例如用户写的文章。
create public int 创建时间戳。
  • 0 - 无实际时间,例如自动生成的资源。(例如RSS站点地图)
  • 60 - 资源创建于1970-01-01 00:01:00。
modify public int 修改时间戳。
  • 0 - 无实际时间,例如API。
  • 60 - 资源修改于1970-01-01 00:01:00。
meta public array 资源元素据。包含Bearweb CMS架构与模板使用的可选数据。
content public string 资源内容。为最小化服务器负载,内容应该是最终输出的内容(对于网页来说,这里应该包含HTML而不是markdown)。如果为null,Bearweb将在资源目录下以url为文件名读取文件。这是为了不在数据库中保存大文件,参考我的博客详述
  • '<div><!-- 我的HTML内容 --></div>' - 一篇文章。
  • 'ABCDEFG' - 一个纯文本文件。
  • 48613FA87E... - 一个二进制文件
  • null - 文件支持资源。Bearweb CMS将在资源目录下以url为文件名读取文件。
  • PHP中string可以是二进制的。
aux public array 资源额外素据。包含模板使用的可选数据。

Bearweb_Site构造方法

参数 默认 可接受类型与例子
url string ''(空字符串)
  • string 'hello/world.html'
category string ''
  • string 'Article' - 这个资源属于一个“Article”。
template ['object', 'blob'] - 对象(文件)模板,直接输出内容。
  • ['page', 'article'] - 使用文章网页模板。
  • '["page", "article"]' - 合法JSON字符串将被解码为['page', 'article']
  • '"page", "article]' - 非法JSON字符串将被解码为默认值['object', 'blob']
owner string ''(空字符串) - 系统所有资源。
  • '' - 系统拥有该资源。
  • 'John' - 名为“John”的用户拥有该资源。
create Bearweb_Site::TIME_NULL (0) - 无实际时间。
  • int 60 - 资源创建于1970-01-01 00:01:00。
  • Bearweb_Site::TIME_CURRENT - 将转化为当前时间戳。
  • Bearweb_Site::TIME_NULL - 无实际时间。
modify Bearweb_Site::TIME_NULL (0) - 无实际时间。
  • int 120 - 资源修改于1970-01-01 00:02:00。
  • Bearweb_Site::TIME_CURRENT - 将转化为当前时间戳。
  • Bearweb_Site::TIME_NULL - 无实际时间。
meta array [] - 空元数据。
  • ['title' => 'Example Title', 'description' => 'This is an example resource.'] - 包括标题和简述的元数据。
  • '{"title": "Example Title", "description": "This is an example resource."}' - 合法JSON字符串将被解码为['title' => 'Example Title', 'description' => 'This is an example resource.']
  • '["title: "Example Title", "description": "This is an example resource.' - 非法JSON字符串将被解码为[]
content string ''(空字符串)
  • string '...' - 输出数据。
  • resource fopen(...) - 指向资源文件夹下以资源url作为文件名的文件指针。
  • 对于文件支持的资源,content变量为文件指针(PHP类型为resource)。只有当使用时才会实际读取该文件。

aux array [] - 空额外数据。
  • ['lang-en' => 'url/en', 'lang-zh' => 'url/zh'] - 包括多语页面的语言选项的额外数据。
  • '{"lang-en": "url/en", "lang-zh": "url/zh"}' - 合法JSON字符串将被解码为['lang-en' => 'url/en', 'lang-zh' => 'url/zh']
  • '[lang-en": "url/en", "lang-zh": "url/zh' - 非法JSON字符串将被解码为[]

access(Bearweb_User $user): bool - 测试用户访问权限

使用$resource->access(Bearweb_User $user): bool来测试用户访问权限,其中:

返回其中之一:

如果一个资源的meta中有定义access,那就是一个启用访问控制的资源。所有者和管理员组用户永远拥有读写权力。对于未启用访问控制的资源,所有的用户和游客都可以读取。对于启用访问控制的资源,只有白名单用户和白名单组才可读取。

validURL(string $uid): bool - 用户提交URL格式检查

要检查用户提交URL格式,使用:


if (!Bearweb_Site::validURL($_SERVER["SCRIPT_URL"])) {
	http_response_code(400);
	exit('Bad URL');
}
	

URL的格式要求为:

query(string $url): ?Bearweb_Site - 查找一个资源

使用Bearweb_Site::query(string $url): ?Bearweb_Site来查找一个资源,其中:

例如:


$resource = Bearweb_Site::query('hello/world.html');
echo $resource ? $resource->content : '404 Not Found';
	

Bearweb CMS将首先查找固定地图,再查找数据库。

返回的Bearweb_Site实例中,content可以是:

  • string - 实际内容。(数据库支持)
  • resource - 文件指针。(文件支持)

content使用PHP getter hook。读取该变量将总会返回实际数据(因为hook将自动执行文件读取)。使用get_mangled_object_vars($resource)['content']来获得原始值。

该方程可能抛出BW_DatabaseServerError

dumpContent(int $len = -1, bool $header = false): void -直接输出内容

虽然数据库支持的资源和文件支持的资源都可以echo $resource->content输出内容,但这对文件支持的资源并不友好。这将需要首先将内容读入内存中,如果文件过大可能造成内存溢出的问题,例如高清大图和视频文件。

使用$resource->dumpContent(int $len = -1, bool $header = false): void来直接输出内容,避免将文件支持的资源读入内存,其中:

例如:


$resource = Bearweb_Site::query('hello/world.png');
$resource->dumpContent(-1, true);
	

该方程为对象(文件)模板优化。

getContentLength(): int - 获得内容大小

使用$resource->getContentLength(): int来获得内容大小,对数据库支持的资源和文件支持的资源都有效。

为了原子性,考虑使用$resource->dumpContent(-1, true)来同时输出内容和长度头。

insert(): void, update(): void, upsert(): void - 创建或修改资源

要创建或修改一个资源,首先创建一个Bearweb_Site实例,并提供资源信息;或是查找一个资源,再修改资源数据。接下来,使用$resource->insert()来创建一个资源,或是$resource->update()来修改一个资源,或是$resource->upsert()创建或修改一个已知的资源。例如:


$resource = new Bearweb_Site(
	url:		'hello/world.html',
	content:	'...',
	... /* Other data */
);
$resource->insert(); // 创建一个资源,如果已存在则会失败
$resource->update(); // 覆盖一个已存在的资源,如果不存在就会失败
$resource->upsert(); // 创建一个资源,如果已存在就修改

$resource = Bearweb_Site::query('John');
$resource->content = 'Foo';
$resource->modify = Bearweb_Site::TIME_CURRENT;
$resource->update(); // 修改一个已存在的资源的特定数据
	

如果content的体积过大(默认分界线为100kB,可以通过Bearweb_Site::Size_FileBlob设置),那么content将被保存在文件中而不是数据库中。该情况下,数据库中content值将为null

你只要把数据字节交给这几个方程就好了。不要手动创建文件,Bearweb CMS内部逻辑会处理好。

不要创建或修改固定地图中能找到的资源。

该方程可能抛出BW_DatabaseServerError

delete(): void - 删除一个资源

要删除一个资源,先创建一个Bearweb_Site实例,只需要提供资源URL。然后,使用$resource->delete()来删除那个资源。例如:


$resource = new Bearweb_Site(
	url:		'hello/world.html',
	... /* Other data will be ignored */
);
$resource->delete();
	

不要删除固定地图中能找到的资源。

该方程可能抛出BW_DatabaseServerError