v28 第 28 批(12 条):名册分组改标签 / 资源合并为两按钮 / 首页定位与主题色 / 笔记富文本双模式
其中 4 条是**上批遗漏的真 bug**(1/4/8/9),根因已逐条定位:
1. 首页日程没定位到红线 —— 真根因:刻度区间**不含当前时刻** → $showNow=false
→ 红线**整个不渲染**,而定位脚本用同一个越界比例算目标,被 clamp 到底部。
修:把当前小时并进区间端点(现在必然落在区间内)。
2. 「看整月」→「更多日程」+ 日程主题色;3. 「手头在做」→「近期任务」、
「任务看板」→「全部任务」+ 任务主题色(注入 --hd-c,不再写死白底)。
4. 任务复选框提示还在 —— 真根因:**JS 在点击后**给状态徽标挂 title
(`badge.setAttribute('title',…)`),上批只去了静态渲染,静态断言永远抓不到。
5. 名册「分组」→「标签」(用户:一个人可能既是小学同学又是初中同学,没法分组):
新增 person_tags(每人一份、一人多标签),名册改**平铺 + 顶部标签筛选**;
🔴 迁移用**一次性哨兵**(第一版用 NOT EXISTS,实测被"用户清空标签"绕过 → 旧分组名被灌回)。
6. 名册卡片按钮加语义底色。
7. 资源四按钮 → **编辑 / 删除**两颗:三块内容进一个带页签的弹框(信息/附件/共享)。
8. 财务多选数字不变 —— 真根因:前端发 `kinds=a,b`(逗号串),服务端 `(array)` 收成
单元素 → 求交为空 → **静默回落整本账**。修:两种形状都收。
9. 微信读书偶发很慢 —— 真根因:syncNow 扫 60 本 × 每本 1~3 次网关调用(单次超时 20s),
全同步阻塞在列表页请求里。修:总时长预算 + 单次超时 8s + 自动/手动分批。
10. 新增全局可见的联想接口库(6 个实测可用来源)+ 三种新解析类型。
11. 导航右上角「设置」:默认进"添加网址 + 全部网址",四类设置收进 ?set=1 视图。
12. 笔记:段落/清单支持 Markdown(自研渲染器,**不允许原始 HTML**),
实时预览走服务端(前后端只一份规则);底部附件上传;清单多级;
列表改**定尺寸卡片网格**(溢出显示摘要、图片缩放下方);按用户要求**去掉标签侧栏**。
This commit is contained in:
+58
-4
@@ -65,6 +65,32 @@ final class WeRead
|
||||
*/
|
||||
public const SYNC_TTL = 21600;
|
||||
|
||||
/**
|
||||
* 一次同步的**总时长预算**(秒)。
|
||||
*
|
||||
* 🔴🔴 v28 用户第 9 条:「读书,同步微信读书后,感觉有时候点击进来很慢,
|
||||
* 需要加载很长时间,后来就好了,过一段时间又慢。」
|
||||
*
|
||||
* 根因(实测推演,症状完全吻合):
|
||||
* 自动同步挂在**读书列表页**上(见 index.php 的 books 路由),
|
||||
* 而 `syncNow()` 最多扫 60 本书,**每一本**都要打 1~3 次网关
|
||||
* (bookInfo / progress / notes,缺一座就补一次封面还要再下图片)。
|
||||
* 单次 `call()` 的超时是 **20 秒**(见 call() 的 CURLOPT_TIMEOUT)——
|
||||
* 于是"6 小时到期的那一次"最坏能挂几分钟:用户点进来就是白屏等待,
|
||||
* 过后(时间戳已写、间隔未到)再进来只读一个时间戳,立刻就快了;
|
||||
* 再过 6 小时又是那一下 —— 这正是用户描述的"时快时慢、过一阵又慢"。
|
||||
*
|
||||
* ⇒ 修法:给整轮同步一个**总预算**。到点就**停下来、正常返回**,
|
||||
* 已处理的部分照常落库,剩下的等下一轮接着做(游标从头再来一遍也无妨:
|
||||
* `matchLocalBook` 与 `store` 都是幂等的,"已存在的会跳过")。
|
||||
* ⚠️ 为什么设 8 秒:这是"用户能忍的一次页面等待"的量级。
|
||||
* 真实耗时取决于网关,宁少勿多 —— 慢一点同步完,比页面打不开好得多。
|
||||
*/
|
||||
public const SYNC_BUDGET = 8;
|
||||
|
||||
/** 每次同步最多扫几本(与预算双保险:预算防"慢",条数防"多") */
|
||||
public const SYNC_MAX_BOOKS = 25;
|
||||
|
||||
// ================================================================
|
||||
// Key 的存取(加密)
|
||||
// ================================================================
|
||||
@@ -140,8 +166,14 @@ final class WeRead
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_POST => true,
|
||||
CURLOPT_POSTFIELDS => $json,
|
||||
CURLOPT_TIMEOUT => 20,
|
||||
CURLOPT_CONNECTTIMEOUT => 8,
|
||||
// 🔴 v28 用户第 9 条:单次调用超时从 20 秒收到 **8 秒**。
|
||||
// 同步一轮要打好几次网关,20 秒 × N 次可以直接把页面拖垮;
|
||||
// 而网关正常时是零点几秒就回的(见 nav_suggest.php 里那批实测:
|
||||
// 同类接口 0.1~1.6 秒)。8 秒足以覆盖"网关偶发慢",又不会让
|
||||
// 一次页面请求长时间挂着。慢的代价只是"这一轮少同步几本",
|
||||
// 下一轮会接着做(幂等)。
|
||||
CURLOPT_TIMEOUT => 8,
|
||||
CURLOPT_CONNECTTIMEOUT => 5,
|
||||
CURLOPT_SSL_VERIFYPEER => true,
|
||||
CURLOPT_HTTPHEADER => [
|
||||
'Authorization: Bearer ' . $key,
|
||||
@@ -685,6 +717,10 @@ final class WeRead
|
||||
* 绝不让"同步失败"把读书页也打不开 —— 外部依赖不能拖死主流程。
|
||||
*
|
||||
* @param bool $force true = 忽略间隔,立刻同步(用户点「立即同步」时用)
|
||||
* 🔴 v28 用户第 9 条:$force 时给**更大的扫描量**(用户是
|
||||
* 主动点的,愿意等;而且手动同步本来就该一次做完整)。
|
||||
* 页面上的自动同步则用小量 —— 它挂在读书列表页上,
|
||||
* 每多扫一本就多一次"打开页面就等"。
|
||||
* @return array{ok:bool,ran:bool,error:string,books:int,notes:int,covers:int,at:string}
|
||||
*/
|
||||
public static function maybeSync(int $familyId, int $personId, bool $force = false): array
|
||||
@@ -698,12 +734,16 @@ final class WeRead
|
||||
if (!$force && $last > 0 && (time() - $last) < self::SYNC_TTL) {
|
||||
return $empty;
|
||||
}
|
||||
// ⚠️ 先记时间再干活:万一下面 20 秒里超时/抛错,也不会在同一个页面
|
||||
// ⚠️ 先记时间再干活:万一下面某一轮超时/抛错,也不会在同一个页面
|
||||
// 反复重试("每次打开读书页都卡 20 秒"是最糟的症状)。
|
||||
// 🔴 v28:现在同步**有总预算**(SYNC_BUDGET),不会再长时间卡住;
|
||||
// 这条"先记时间"依然要留 —— 它是"网关整个挂掉时别每次访问都重试"的兜底。
|
||||
UserSetting::set($personId, self::SYNC_AT_KEY, (string)time());
|
||||
|
||||
try {
|
||||
$res = self::syncNow($familyId, $personId);
|
||||
// 自动同步只扫一小批(页面等待),手动同步扫完整批(用户愿意等)
|
||||
$limit = $force ? 60 : self::SYNC_MAX_BOOKS;
|
||||
$res = self::syncNow($familyId, $personId, $limit);
|
||||
} catch (\Throwable $e) {
|
||||
return ['ok' => false, 'ran' => true, 'error' => $e->getMessage(),
|
||||
'books' => 0, 'notes' => 0, 'covers' => 0, 'at' => date('Y-m-d H:i:s')];
|
||||
@@ -727,6 +767,16 @@ final class WeRead
|
||||
*/
|
||||
public static function syncNow(int $familyId, int $personId, int $maxBooks = 60): array
|
||||
{
|
||||
/* 🔴 v28 用户第 9 条:**整轮的总时长预算**(见 SYNC_BUDGET 的注释)。
|
||||
* 到点就停、正常返回已处理的部分,剩下的下一轮继续。
|
||||
* ⚠️ 用"开始时刻 + 每轮循环都检查"而不是"包一个超时异常":
|
||||
* PHP 的 curl 超时是按**单次调用**算的,没有"整个函数超时"这种机制;
|
||||
* 要在中途刹车,只能在循环里自己判。
|
||||
* ⚠️ 判据放在**每本书的开头**(不是每本书之间某处):一本书内部还会打
|
||||
* 2~3 次网关,若在书中途判断就会留下半本书(字段补一半)。
|
||||
* 整本为单位地停,重来时那本书会被幂等地重做一遍。 */
|
||||
$deadline = microtime(true) + self::SYNC_BUDGET;
|
||||
|
||||
$nb = self::notebooks($personId);
|
||||
if (!$nb['ok']) {
|
||||
return ['ok' => false, 'error' => (string)$nb['error'], 'books' => 0,
|
||||
@@ -738,6 +788,10 @@ final class WeRead
|
||||
if ($scanned >= $maxBooks) {
|
||||
break;
|
||||
}
|
||||
// 总预算到点 → 停在这里(已处理的都已落库;剩下的下一轮再做)
|
||||
if (microtime(true) >= $deadline) {
|
||||
break;
|
||||
}
|
||||
$title = trim((string)$b['title']);
|
||||
if ($title === '') {
|
||||
continue;
|
||||
|
||||
Reference in New Issue
Block a user