照片、视频和语音消息的存储位置
从 27.1.1 版本起,Agency Pad 的每一张照片、每一段视频、每一次屏幕录制和每一条语音消息都由脚本自己保存。存储位置由你决定:
| 存储方式 | 设置 | 适用场景 |
|---|---|---|
| 本地(默认) | 无需设置。安装后即可使用。 | 所有服务器。文件保存在你自己的服务器上,存放在你的数据库中。 |
| Cloudflare R2 | 只需设置一次,大约 10 分钟。10 GB 以内免费。 | 视频很多或玩家人数非常多的服务器。 |
Fivemanage 和 Discord 不再用作存储。Discord webhook 仍可用于日志。
本地存储(默认)
无需任何设置。脚本在首次启动时会在你的数据库中创建两张表(agency_media 和 agency_media_parts),并把文件保存在其中。你只需要 oxmysql,而脚本本来就需要它。
这对你意味着:
- 无需账号,无需 API 密钥,无需额外的资源。没有任何内容在你的服务器之外运行。
- 更新很安全。文件保存在数据库中,而不是资源文件夹里,所以更新时替换文件夹不会丢失任何照片。
- 你的数据库备份包含这些媒体文件。数据库的体积会比文件本身大约多出三分之一。一张照片约 150 KB。
- Agency Phone 和 Agency Pad 共用存储。用其中一个拍的照片也会出现在另一个上,包括与 Agency Cloud 同步的照片。
- Agency Companion 应用也会显示这些内容,支持 iOS、Android 和网页版应用。这同样无需任何设置。
速度
文件通过 FiveM 自身的连接传输,不会阻塞游戏。FiveM 限制了玩家向服务器发送数据的速度(约每秒 165 KB),因此:
| 内容 | 保存大约需要 |
|---|---|
| 照片 | 1 秒 |
| 语音消息 | 不到 1 秒 |
| 15 秒的视频片段 | 20 秒(相机会以百分比显示进度) |
向其他玩家显示文件很快,一段完整的视频几秒钟就能送达。如果你的服务器录制大量视频,Cloudflare R2 几乎可以即时保存视频片段。
config.lua 中的设置
AgencyPadConfig.MediaStorage = {
Provider = 'local', -- 'local' or 'cloudflare'
MaxFileMegabytes = 10, -- largest single file (clips, recordings)
TransferKilobytesPerSecond = 1000, -- 'local': how fast files are sent to players
Local = {
RetentionDays = 30, -- delete files older than this (0 = keep forever)
MaxStorageGigabytes = 10, -- delete the oldest files above this (0 = no limit)
},
}
| 设置项 | 作用 |
|---|---|
RetentionDays | 超过这个天数的文件会每小时自动删除一次。被删除的照片在相册中会显示一个小占位图。设为 0 则永久保留全部文件。 |
MaxStorageGigabytes | 当所有文件的总大小超过这个值时,会优先删除最旧的文件。设为 0 表示不限制。 |
MaxFileMegabytes | 更大的文件会被拒绝。这也限制了视频片段和屏幕录制。 |
TransferKilobytesPerSecond | 向玩家发送文件时每位玩家的速度。游戏始终拥有优先权。 |
Cloudflare R2 分步指南
Cloudflare R2 每月提供 10 GB 存储、100 万次上传和 1000 万次下载的免费额度,而且下载永远不收费。启用 R2 时,Cloudflare 会要求你绑定付款方式,即使使用免费方案也一样。只有超出免费额度时才需要付费。
1. 启用 R2
- 登录 dash.cloudflare.com(免费账号即可)。
- 在左侧菜单中打开 Storage & databases,然后打开 R2 Object Storage。
- 添加 R2 订阅。概览中显示当前应付 $0.00。接受条款并点击 Activate。
2. 创建存储桶
- 点击 Create bucket。
- 为它命名,例如
myserver-media。名称之后无法更改。 - 位置保持 Automatic,存储类别保持 Standard。
- 点击 Create bucket。
3. 让文件可以被查看
- 打开你的存储桶,进入 Settings。
- 在 Public Development URL 下点击 Enable,输入
allow并确认。 - 复制出现的地址,格式类似
https://pub-1a2b3c….r2.dev。这就是你的 PublicUrl。
对于访问量很大的服务器,Cloudflare 会对 r2.dev 地址进行限制。如果你在 Cloudflare 上有自己的域名,可以在同一位置的 Custom Domains 下添加它,并改用该地址作为 PublicUrl。
4. 允许从游戏中上传
- 仍在 Settings 中,找到 CORS Policy 并点击 Add。
- 清空文本框中的所有内容,粘贴下面的内容,然后点击 Save:
[
{
"AllowedOrigins": ["https://cfx-nui-agency-phone", "https://cfx-nui-agency-pad"],
"AllowedMethods": ["PUT", "GET"],
"AllowedHeaders": ["*"],
"MaxAgeSeconds": 3600
}
]
如果没有这一步,游戏将无法上传,照片会保存失败。如果你重命名了资源文件夹,请把你的文件夹名称写在 cfx-nui- 后面。
5. 创建密钥
- 返回 R2 Object Storage,点击 Manage API tokens。
- 点击 Create API token(账号级令牌即可)。
- 权限选择:Object Read & Write。
- 在存储桶选项下,只选择你自己的存储桶。
- 点击 Create。Cloudflare 只会显示一次 Access Key ID 和 Secret Access Key,请现在就把两者都复制下来。
- 你的 Account ID 显示在 R2 概览页,它也是 S3 地址的第一部分(
https://<Account ID>.r2.cloudflarestorage.com)。
6. 在脚本中填写
密钥要填入 config_server.lua。这个文件只由服务器读取,不会发送给玩家。不要把它们放进 config.lua:那个文件会发送给每一位玩家。
-- config_server.lua
AgencyPadServerConfig.Cloudflare = {
AccountId = 'your account id',
Bucket = 'myserver-media',
AccessKeyId = 'your access key id',
SecretAccessKey = 'your secret access key',
PublicUrl = 'https://pub-1a2b3c....r2.dev',
}
然后在 config.lua 中设置存储方式,并重启该资源:
AgencyPadConfig.MediaStorage = {
Provider = 'cloudflare',
...
}
服务器控制台现在会显示 Media: storage: Cloudflare R2, bucket myserver-media。如果缺少了什么,控制台会指出缺少的内容,在问题修复之前脚本会继续保存到本地,所以相机永远不会停止工作。
同时使用 Agency Phone 和 Agency Pad?两者可以使用同一个存储桶和同一个密钥,各自把文件保存在自己的文件夹中。
检查是否正常工作
在服务器控制台中(或以管理员身份在游戏中)运行下面的命令,并使用一位在线玩家的 ID:
padmediatest 1
该玩家的 Agency Pad 会保存一张小的测试图片,像其他玩家一样把它重新加载回来并显示,再播放一条简短的语音消息。控制台会返回:
Media test passed for player 1 in 1361 ms: upload ok, download ok, shown as picture ok, app markup and backgrounds ok, voice message plays ok
加上 big 还可以额外测试一段 6 MB 的视频片段:padmediatest 1 big。
常见问题与解决办法
| 你看到的现象 | 解决办法 |
|---|---|
控制台中出现 could not create the media tables | oxmysql 没有运行,或无法连接到数据库。请在 agency-pad 之前启动 oxmysql。 |
Provider is 'cloudflare', but config_server.lua is missing ... | 五个值中有一个为空,或者 PublicUrl 不是以 https:// 开头。请检查第 6 步。 |
照片保存失败,提示 Cloudflare HTTP 403 | 该密钥对这个存储桶没有写入权限,或者存储桶名称有误。请检查第 5 步。 |
使用 Cloudflare 时照片保存失败,提示 Failed to fetch | 缺少第 4 步中的 CORS 策略,或者其中的资源名称不一致。 |
| 使用 Cloudflare 时已保存的照片不显示 | 第 3 步中的 Public Development URL 没有启用,或者 PublicUrl 指向了另一个存储桶。 |
| 视频保存需要很长时间 | 本地存储下这是正常现象,参见上文的速度部分。如需即时保存视频片段,请使用 Cloudflare R2。 |
| 旧照片不见了 | 被 RetentionDays 删除了。请调大该值或设为 0。 |
从 Fivemanage 或 Discord 迁移
无需迁移任何数据。之前保存的照片保留原来的链接,只要对应的服务仍然提供这些文件,它们就仍然可以查看。所有新内容都会保存到你所选择的存储中。旧的设置项 MediaStorageProvider、FiveManageApiUrl、FiveManageApiKey 和 ScreenshotWebhook 会被忽略,可以从你的配置中删除。
安全
- 只有连接到你服务器的玩家才能上传。每次上传都会先进行声明,并检查类型和大小。
- 内容会根据开头的几个字节进行检查。网页或脚本永远无法伪装成照片被存储。
- 文件名是 128 位随机值,无法被猜到,也无法被列出。
- 上传和下载按玩家分别限速。
- 使用 Cloudflare 时,每次上传都会获得一个独立的链接,有效期 5 分钟,只对应一种类型、一种大小的一个文件。密钥永远不会离开你的服务器。
