Framadate 1.1.19 排错 FAQ
Framadate 1.1.19 实测排错问答:composer framagit 超时与代理、use_smtp 硬编码致建票 500 与孤儿票、PHP 8.1 strftime CSV 500、ICS 参数 base64url 编码、投票 control 哈希与 choices[] 字段、save 分支语义、停库自动降级、迁移幂等等 13 个真实故障,每条附报错原文与修复回显。
Framadate 1.1.19 排错 FAQ
目标:把 Framadate 1.1.19(Ubuntu 22.04.5 + Apache 2.4.52 + PHP 8.1.2 + MariaDB 10.6.23,监听 8145)部署与试用中真实踩到的 13 个坑整理成「现象 → 定位 → 处置」问答,每条报错原文与修复回显均取自实测环境。部署步骤见 《Ubuntu 22.04 安装 Framadate 1.1.19 指南》,日常运维见 《Framadate 1.1.19 运维与升级手册》。
一、环境信息
- 操作系统:Ubuntu 22.04.5 LTS;Web:Apache 2.4.52(mod_php,
Listen 8145) - PHP:8.1.2-1ubuntu2.28;数据库:MariaDB 10.6.23(库
framadate,账号framadate,密码q1w2e3r4) - 应用:Framadate 1.1.19,目录
/var/www/framadate,表前缀fd_ - 现场数据:日期投票
dvdEakcXnbWf7eXf(管理链接后缀 24 位 admin_iddvdEakcXnbWf7eXfgPd6E5R4),4 条投票记录(Alice/Bob/Charlie/Dave),control 哈希d45ffae7e20458595a509d3781436602
二、排错工具箱
三条日志定位一切,先记住位置再查 FAQ:
ls -l /var/log/apache2/framadate_error.log /var/log/apache2/framadate_access.log /var/www/framadate/admin/stdout.log
-rw-r--r-- 1 root root 8537 Nov 1 14:53 /var/log/apache2/framadate_access.log
-rw-r--r-- 1 root root 70146 Nov 1 14:53 /var/log/apache2/framadate_error.log
-rw-r--r-- 1 www-data www-data 254 Nov 1 14:21 /var/www/framadate/admin/stdout.log
(access/error 随流量增长,stdout.log 为应用事件日志,实测全文仅两条 [CREATE_POLL] 行。)
- HTTP 500 白屏:一律先
tail -f /var/log/apache2/framadate_error.log复现一次,PHP Fatal 原文会带文件与行号; - 数据不对:直接查
fd_poll/fd_slot/fd_vote/fd_comment四表 +fd_framadate_migration; - 建票留痕:
admin/stdout.log的[CREATE_POLL] id:tXeTu4G6InJoAHGd这类行可反查孤儿票。
三、症状速查表
| 症状 | 根因 | 跳转 |
|---|---|---|
| composer install 卡在 git clone 超时 | framagit.org 直连不通 | 4.1 |
| 建票「创建」按钮 500 | 模板硬编码 use_smtp=true 且无 SMTP | 4.2 |
| CSV 导出 500 | PHP 8.1 strftime 类型收紧 | 4.3 |
| ICS 导出 500/404 | 参数须 base64url("d-m-Y|H:i") | 4.4 |
投票提示 Le nom n'est pas valide. | control 哈希缺失/错误 | 4.5 |
| 投票 POST 500 filterArray null | 缺 choices[] 数组字段 | 4.6 |
| 投票变成「编辑旧票」 | save 字段语义误用 | 4.7 |
| 全站 302 到 maintenance.php | MariaDB 停机(自动降级) | 4.8 |
| install.php 总被弹回首页 | 已配置,防重复安装 | 4.9 |
| migration 页全是 Passé | 迁移判重幂等,正常 | 4.10 |
| 投票短链 404 查询串正常 | rewrite 三件套缺一 | 4.11 |
| 500 且含 Permission denied tpl_c | Smarty 缓存目录不可写 | 4.12 |
| GitHub API 查 releases 返回 404 | GitHub 同步副本 API 异常 | 4.13 |
四、常见问题
4.1 composer install 卡在 git clone 超时
现象(实测原文):
Failed to execute git clone --mirror 'https://framagit.org/framasoft/framadate/o80-i18n'
... Failed to connect to framagit.org port 443 after 137538 ms
定位:14 个生产依赖里 13 个走 packagist(直连可用),唯独 o80/i18n 钉在 framagit.org 的 dev-develop 分支、必须现场 git clone,而该站直连实测超时。
处置:走代理重试(实测 192.168.1.72:7897 可用、192.168.1.62:7897 不可用):
export http_proxy=http://192.168.1.72:7897 https_proxy=http://192.168.1.72:7897
composer install --no-dev
成功回显(composer install --no-dev 实测尾 4 行;首次安装时为 14 包逐一 Downloading):
Package webmozart/path-util is abandoned, you should avoid using it. Use symfony/filesystem instead.
Generating autoload files
7 packages you are using are looking for funding.
Use the `composer fund` command to find out more!
4.2 建票第二步点「创建」HTTP 500(SMTP connect() failed)
现象:三步向导最后提交直接 500,错误日志实测原文:
PHP Fatal error: Uncaught PHPMailer\PHPMailer\Exception: SMTP connect() failed.
https://github.com/PHPMailer/PHPMailer/wiki/Troubleshooting
in /var/www/framadate/vendor/phpmailer/phpmailer/src/PHPMailer.php:1982
#2 /var/www/framadate/app/classes/Framadate/Services/MailService.php(67)
#3 /var/www/framadate/create_date_poll.php(213): Framadate\Services\MailService->send()
定位:安装向导生成的 config.php 把 'use_smtp' => true 硬编码写入(与 sample 的 false 不一致,属模板缺陷),create_date_poll.php 第 213 行建票后立即调 MailService->send(),本机无 SMTP 即抛异常。
处置(两步,缺一不可):
sed -i "s/'use_smtp' => true/'use_smtp' => false/" /var/www/framadate/app/inc/config.php
更隐蔽的坑:500 发生在投票已写库之后——票建好了但管理员拿不到 24 位管理链接,成为孤儿票。从 stdout.log 反查孤儿 ID 清理(实测该文件留下两条 CREATE_POLL,第一条即孤儿):
mysql -uframadate -pq1w2e3r4 framadate -e \
"DELETE FROM fd_slot WHERE poll_id='tXeTu4G6InJoAHGd';
DELETE FROM fd_poll WHERE id='tXeTu4G6InJoAHGd';"
重提交即 302 到管理链接页,实测成功票 dvdEakcXnbWf7eXf 入库。
4.3 CSV 导出 500:strftime TypeError(PHP 8.1 兼容)
现象(错误日志实测原文):
PHP Fatal error: Uncaught TypeError: strftime(): Argument #2 ($timestamp) must be
of type ?int, string given in /var/www/framadate/exportcsv.php:80
定位:PHP 8.1 起 strftime() 第二参强制 ?int,而 fd_slot.title(时间戳以字符串存库,实测值 1794092400)取出即 string。项目 2021-12 停更,官方不会修,只能本地打补丁:
cp /var/www/framadate/exportcsv.php /var/www/framadate/exportcsv.php.bak
sed -i 's/strftime(\$format, \$slot->title)/strftime($format, (int) $slot->title)/' \
/var/www/framadate/exportcsv.php
curl -s -o /dev/null -w "%{http_code} %{content_type}\n" \
"http://localhost:8145/exportcsv.php?poll=dvdEakcXnbWf7eXf"
200 text/csv; charset=utf-8
补丁后实测导出正文:"Alice","Oui","Non"、"Bob","Si nécessaire","Oui"。附带说明:表头出现若干 01/01/1970 列不是故障——建票日期网格勾选 1 天会生成 7 天槽位行,未选日子的 fd_slot.title 为空串,(int) 后成 0 即纪元零点;导出有效数据不受影响,介意的话删票重建时只勾选需要的日期。
4.4 ICS 导出 500/404:参数不是时间戳,是 base64url("d-m-Y|H:i")
现象:凭直觉把时间戳拼进 get_ical_file= 参数得到 500(页面回显 Internal error);或 URL 漏掉投票 ID 前缀、直接请求 /action/ics/参数 得到 404(实测 ics 404 text/html; charset=iso-8859-1)。
定位:studs.php 第 224~226 行实测源码——参数先 Utils::base64url_decode(),再按 | 拆成「日|时:分」两段:
if (isset($_GET['get_ical_file'])) {
$dayAndTime = (string)filter_input(INPUT_GET, 'get_ical_file');
$dayAndTime = Utils::base64url_decode($dayAndTime);
$elements = explode("|", $dayAndTime);
且日期段必须匹配 /^\d{2}-\d{2}-\d{4}$/(d-m-Y 格式,日在前)。
处置:按格式造参数再请求(实测):
ICS=$(printf "08-11-2026|14:00" | base64 -w0 | tr "+/" "-_" | tr -d "=")
curl -s -o /dev/null -w "%{http_code} %{content_type}\n" \
"http://localhost:8145/dvdEakcXnbWf7eXf/action/get_ical_file/$ICS"
curl -s "http://localhost:8145/dvdEakcXnbWf7eXf/action/get_ical_file/$ICS" | head -6
200 text/calendar;charset=UTF-8
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Framasoft//Framadate//EN
CALSCALE:GREGORIAN
BEGIN:VEVENT
DTSTART;TZID=Europe/Paris:20261108T140000
注意 URL 里的动作名是 get_ical_file(.htaccess 的 ^([a-zA-Z0-9-]+)/action/([a-zA-Z_-]+)/(.+)$ 规则把它重写为 studs.php?poll=..&get_ical_file=..),查询串形态 studs.php?poll=dvdEakcXnbWf7eXf&get_ical_file=$ICS 同样可用。
4.5 投票提交提示 Le nom n'est pas valide.(control 哈希校验失败)
现象(实测复现:故意带错误 control POST):
curl -s -b jar "http://localhost:8145/studs.php?poll=dvdEakcXnbWf7eXf" \
-d "poll=dvdEakcXnbWf7eXf&name=Charlie&control=WRONGHASH1234567890abcdef1234" \
| grep -oE "alert-danger[^<]*<[^<]*"
alert-danger hidden-print" role="alert">Le nom n'est pas valide.<button type="button" class="close" data-dismiss="alert" aria-label="Fermer">
定位:报错文案指向「名字无效」,实际是 studs.php 第 162 行 filterMD5($_POST['control']) 校验失败后的统一提示——control 是服务端按票数据算出的 32 位 md5(本票实测 d45ffae7e20458595a509d3781436602),嵌在投票页隐藏域里防伪造。脚本化投票漏带或带错该哈希,就会被这句误导性文案挡回来。
处置:同一会话先 GET 投票页取隐藏域,再原样回传(实测表单提取命令):
curl -s -c jar "http://localhost:8145/studs.php?poll=dvdEakcXnbWf7eXf" -o v.html
CONTROL=$(grep -oE 'name="control" value="[a-f0-9]{32}"' v.html | grep -oE '[a-f0-9]{32}')
4.6 投票 POST 500:filterArray() Argument #1 must be of type array, null given
现象(错误日志实测原文):
PHP Fatal error: Uncaught TypeError: Framadate\Services\InputService::filterArray():
Argument #1 ($arr) must be of type array, null given, called in
/var/www/framadate/studs.php on line 161
定位:studs.php 第 161 行直接消费 $_POST['choices'] 数组——每个时间槽一个 choices[] 元素,取值 0(拒绝)/1(待定)/2(同意)(CHOICE_REGEX 校验)。字段名拼错(如 check4=20)或整个不带,$_POST['choices'] 为 null 即 500。
处置(实测成功命令,Charlie、Dave 先后入库):
curl -s -b jar -o /dev/null -w "%{http_code}\n" \
"http://localhost:8145/studs.php?poll=dvdEakcXnbWf7eXf" \
-d "poll=dvdEakcXnbWf7eXf&name=Dave&save=&control=$CONTROL&choices[]=1&choices[]=2"
mysql -uframadate -pq1w2e3r4 framadate -e "SELECT name,choices FROM fd_vote;"
200
name choices
Alice 20
Bob 12
Charlie 20
Dave 12
fd_vote.choices 是各位表态的拼接串:20 = 槽 1 同意 + 槽 2 拒绝,12 = 槽 1 待定 + 槽 2 同意。
4.7 投票提交后变成「编辑旧票」或毫无反应:save 字段语义
定位:studs.php 源码实测两个分支——第 123 行 if (!empty($_POST['save'])) 走「保存对旧票的编辑」(要求 save 非空且为旧票 uniqId 语境);第 159 行 elseif (isset($_POST['save'])) 才是「新增投票」,条件是 save 存在但为空值。
处置:脚本化新增投票必须显式带 save=(空值);完全省略该字段则两个分支都不命中,POST 看似 200 实际什么都没写库;给 save 填非空值则被当编辑旧票处理。4.6 节的成功命令即为标准形态。
4.8 全站突然 302 到 maintenance.php
现象(实测停库复现):
systemctl stop mariadb
curl -s -o /dev/null -w "%{http_code} -> %{redirect_url}\n" http://localhost:8145/
curl -sL http://localhost:8145/ | grep -oE "SQLSTATE[^<]*" | head -1
302 -> http://localhost:8145/maintenance.php
SQLSTATE[HY000] [2002] No such file or directory
定位:这不是维护开关,是应用连不上数据库时的自动降级。[2002] No such file or directory 说明 PDO 走 socket 而 mariadb 服务没跑;若配置 host=localhost 想走 TCP,可改 DSN 为 127.0.0.1 排查。
处置:systemctl start mariadb 后实测首页恢复 200。主动维护(DDL、换存储)就用「停库即优雅降级」当维护模式用,见运维篇 6.4 节。
4.9 访问 install.php 总是被弹回首页
现象:curl -sI http://localhost:8145/admin/install.php 实测返回:
HTTP/1.1 302 Found
Location: http://localhost:8145/
定位:app/inc/config.php 已存在时安装器拒绝重入,属安全设计非故障。需要重配数据库直接改 config.php 文件即可,没有「重装」一说。
4.10 migration 页全是 Passé,是不是没执行
现象:升级/重跑 admin/migration.php 后页面 14 行全部显示 Passé。
定位:fd_framadate_migration 记录表判重(实测 13 条记录、migration重跑 200),Passé = 该迁移已在库里执行过、本次跳过——这正是幂等设计,反复重跑无副作用。只有库里缺的迁移才会真正执行并显示成功。
4.11 投票短链 404,查询串却正常
定位:URL_PROPRE、.htaccess、AllowOverride All、mod_rewrite 四者缺一,/dvdEakcXnbWf7eXf 就 404 而 studs.php?poll=dvdEakcXnbWf7eXf 正常。逐项核对:
ls -l /var/www/framadate/.htaccess
grep -n "AllowOverride" /etc/apache2/sites-available/framadate.conf
apache2ctl -M 2>/dev/null | grep rewrite
-rw-r--r-- 1 www-data www-data 702 Nov 1 14:18 /var/www/framadate/.htaccess
6: AllowOverride All
rewrite_module (shared)
改完 systemctl reload apache2,实测短链复归 200。
4.12 500 且日志含 Permission denied(tpl_c)
定位:Smarty 把模板编译到 tpl_c/,Web 身份(www-data)不可写即报错。实测健康权限为 www-data:www-data 755。
处置:
chown -R www-data:www-data /var/www/framadate/tpl_c
整目录属主建议一次性到位:chown -R www-data:www-data /var/www/framadate(安装篇 4.4 节同款命令)。
4.13 用 GitHub API 查 releases/tags/1.1.19 返回 404
现象:api.github.com/repos/framasoft/framadate/releases/tags/1.1.19 实测返回 404,误以为版本不存在。
定位:GitHub 上 framasoft/Framadate 只是同步副本(主战场在 framagit.org),其 Releases API 数据不全;但 tag 归档下载(archive/refs/tags/1.1.19.tar.gz)与 releases.atom 订阅源均正常。
处置:版本溯源改走 releases.atom(实测拿到 1.1.19=2021-12-23、1.1.18=2021-12-21、1.1.17=2021-10-18)与仓库页面 HTML;star/语言占比等指标用 shields.io 徽章接口取(实测 stars=103、PHP=45.8%)。
五、参考链接
- 《Framadate 1.1.19 介绍与选型指南》(架构与数据模型)
- 《Ubuntu 22.04 安装 Framadate 1.1.19 指南》(三方案安装与 .htaccess)
- 《Framadate 1.1.19 运维与升级手册》(备份恢复/清理机制/升级回滚)
- PHPMailer 排错维基:https://github.com/PHPMailer/PHPMailer/wiki/Troubleshooting (4.2 报错原文自带链接)
- PHP 8.1 迁移说明(strftime 签名收紧):https://www.php.net/manual/en/function.strftime.php
- 源码仓库:https://github.com/framasoft/Framadate ;官方主仓库:https://framagit.org/framasoft/framadate