no message

This commit is contained in:
hajimi
2026-06-22 01:31:20 +08:00
parent 9994c71374
commit ee9a5a3d03
9 changed files with 112 additions and 14 deletions
+15 -1
View File
@@ -1,7 +1,21 @@
UPDATE `la_system_menu` child
JOIN `la_system_menu` parent
ON parent.`type` = 'M'
AND parent.`paths` = 'match'
SET child.`pid` = parent.`id`,
child.`paths` = 'match_live',
child.`component` = 'match/live/index',
child.`selected` = '/match/lists',
child.`is_show` = 0,
child.`update_time` = UNIX_TIMESTAMP()
WHERE child.`perms` = 'match.matchLive/index'
AND child.`pid` <> parent.`id`;
INSERT INTO `la_system_menu` (`pid`, `type`, `name`, `icon`, `sort`, `perms`, `paths`, `component`, `selected`, `params`, `is_cache`, `is_show`, `is_disable`, `create_time`, `update_time`) INSERT INTO `la_system_menu` (`pid`, `type`, `name`, `icon`, `sort`, `perms`, `paths`, `component`, `selected`, `params`, `is_cache`, `is_show`, `is_disable`, `create_time`, `update_time`)
SELECT parent.`id`, 'C', '直播线路管理', '', 0, 'match.matchLive/index', 'match_live', 'match/live/index', '/match/lists', '', 0, 0, 0, UNIX_TIMESTAMP(), UNIX_TIMESTAMP() SELECT parent.`id`, 'C', '直播线路管理', '', 0, 'match.matchLive/index', 'match_live', 'match/live/index', '/match/lists', '', 0, 0, 0, UNIX_TIMESTAMP(), UNIX_TIMESTAMP()
FROM `la_system_menu` parent FROM `la_system_menu` parent
WHERE parent.`perms` = 'match.match/lists' WHERE parent.`type` = 'M'
AND parent.`paths` = 'match'
AND NOT EXISTS ( AND NOT EXISTS (
SELECT 1 FROM `la_system_menu` SELECT 1 FROM `la_system_menu`
WHERE `perms` = 'match.matchLive/index' WHERE `perms` = 'match.matchLive/index'
+31
View File
@@ -0,0 +1,31 @@
UPDATE `la_system_menu`
SET
`name` = '自注册账号',
`paths` = 'lists',
`component` = 'consumer/lists/index',
`params` = '{\"account_scope\":\"registered\"}',
`is_show` = 1,
`update_time` = UNIX_TIMESTAMP()
WHERE `perms` = 'user.user/lists'
AND `component` = 'consumer/lists/index';
INSERT INTO `la_system_menu` (`pid`, `type`, `name`, `icon`, `sort`, `perms`, `paths`, `component`, `selected`, `params`, `is_cache`, `is_show`, `is_disable`, `create_time`, `update_time`)
SELECT COALESCE((SELECT `id` FROM `la_system_menu` WHERE `paths` = 'consumer' AND `type` = 'M' LIMIT 1), 117),
'C', '系统账号', '', 95, 'user.user/lists', 'system_lists', 'consumer/lists/index', '', '{\"account_scope\":\"system\"}', 0, 1, 0, UNIX_TIMESTAMP(), UNIX_TIMESTAMP()
WHERE NOT EXISTS (
SELECT 1 FROM `la_system_menu`
WHERE `paths` = 'system_lists'
AND `component` = 'consumer/lists/index'
);
INSERT INTO `la_system_role_menu` (`role_id`, `menu_id`)
SELECT 1, m.`id`
FROM `la_system_menu` m
WHERE m.`paths` IN ('lists', 'system_lists')
AND m.`component` = 'consumer/lists/index'
AND NOT EXISTS (
SELECT 1
FROM `la_system_role_menu` rm
WHERE rm.`role_id` = 1
AND rm.`menu_id` = m.`id`
);
+6 -5
View File
@@ -21,10 +21,11 @@ def main() -> None:
assert 'v-if="liveList.length"' in detail_source, "match detail should render live_list block with v-if" assert 'v-if="liveList.length"' in detail_source, "match detail should render live_list block with v-if"
assert 'v-else-if="thirdPartyLiveUrl"' in detail_source, "match detail should keep thirdPartyLiveUrl fallback with v-else-if" assert 'v-else-if="thirdPartyLiveUrl"' in detail_source, "match detail should keep thirdPartyLiveUrl fallback with v-else-if"
assert "MatchLivePopup" in detail_source, "match detail should use MatchLivePopup" assert 'v-if="showLiveLineSelector"' in detail_source, "match detail should render inline live line selector"
assert "fetch_status" in detail_source, "match detail should reference fetch_status" assert "fetch_status" in detail_source, "match detail should reference fetch_status"
assert "last_fetch_at" in detail_source, "match detail should reference last_fetch_at" assert "last_fetch_at" in detail_source, "match detail should reference last_fetch_at"
assert "update_time" in detail_source, "match detail should reference update_time" assert "update_time" in detail_source, "match detail should reference update_time"
assert ':show-progress="false"' in detail_source, "match detail live video should hide time progress"
assert "default_play_url" in popup_source, "popup should prefer default_play_url" assert "default_play_url" in popup_source, "popup should prefer default_play_url"
assert "stream_options" in popup_source, "popup should support stream_options" assert "stream_options" in popup_source, "popup should support stream_options"
assert "/pages/webview/webview?url=" in popup_source, "selected stream should route through existing webview page" assert "/pages/webview/webview?url=" in popup_source, "selected stream should route through existing webview page"
@@ -39,10 +40,10 @@ def main() -> None:
assert field_name in popup_source, f"popup should cover {field_name}" assert field_name in popup_source, f"popup should cover {field_name}"
assert field_name in api_source, f"match live types should cover {field_name}" assert field_name in api_source, f"match live types should cover {field_name}"
assert re.search(r"const\s+defaultPlayUrl\s*=\s*normalizeUrl\(props\.line\?\.default_play_url\)", popup_source), \ assert "push(line.default_play_url)" in popup_source, \
"popup should explicitly prefer props.line?.default_play_url" "popup should explicitly prefer line.default_play_url in candidate collection"
assert re.search(r"defaultPlayUrl.*?resetSelected", popup_source, re.S), \ assert "selectedLineKey.value = lineOptions.value[0]?.key || ''" in popup_source, \
"popup selection reset should prefer default_play_url" "popup should reset to the first available line option"
assert re.search(r"fetch_status.*last_fetch_at", popup_source, re.S) or "update_time" in popup_source, \ assert re.search(r"fetch_status.*last_fetch_at", popup_source, re.S) or "update_time" in popup_source, \
"popup metadata should use fetch_status and last_fetch_at/update_time" "popup metadata should use fetch_status and last_fetch_at/update_time"
assert re.search(r"normalized === '0'.*待抓取", popup_source, re.S), \ assert re.search(r"normalized === '0'.*待抓取", popup_source, re.S), \
+16 -8
View File
@@ -23,7 +23,7 @@ def main() -> None:
assert MENU_SQL.is_file(), f"missing menu seed file: {MENU_SQL}" assert MENU_SQL.is_file(), f"missing menu seed file: {MENU_SQL}"
menu_sql = MENU_SQL.read_text(encoding="utf-8") menu_sql = MENU_SQL.read_text(encoding="utf-8")
normalized_menu_sql = " ".join(menu_sql.split()) normalized_menu_sql = " ".join(menu_sql.split())
first_insert_block = menu_sql.split(";", 1)[0] + ";" first_insert_block = "INSERT INTO `la_system_menu`" + menu_sql.split("INSERT INTO `la_system_menu`", 1)[1].split(";", 1)[0] + ";"
normalized_first_insert = " ".join(first_insert_block.split()) normalized_first_insert = " ".join(first_insert_block.split())
assert MATCH_LIVE_PAGE.is_file(), f"missing page file: {MATCH_LIVE_PAGE}" assert MATCH_LIVE_PAGE.is_file(), f"missing page file: {MATCH_LIVE_PAGE}"
@@ -111,8 +111,8 @@ def main() -> None:
"match.matchLive/detail", "match.matchLive/detail",
"match.matchLive/add", "match.matchLive/add",
"match.matchLive/edit", "match.matchLive/edit",
"match.match/lists",
"match.matchLive/delete", "match.matchLive/delete",
"/match/lists",
): ):
assert needle in menu_sql, f"{MENU_SQL} missing permission token: {needle}" assert needle in menu_sql, f"{MENU_SQL} missing permission token: {needle}"
@@ -127,20 +127,28 @@ def main() -> None:
assert ( assert (
"FROM `la_system_menu` parent" in menu_sql "FROM `la_system_menu` parent" in menu_sql
and "parent.`perms` = 'match.match/lists'" in menu_sql and "parent.`type` = 'M'" in menu_sql
), f"{MENU_SQL} must bind the match live menu to parent match.match/lists explicitly" and "parent.`paths` = 'match'" in menu_sql
), f"{MENU_SQL} must bind the match live menu to the top-level match catalogue explicitly"
assert ( assert (
"SELECT parent.`id`, 'C', '直播线路管理'" in menu_sql "SELECT parent.`id`, 'C', '直播线路管理'" in menu_sql
), f"{MENU_SQL} must insert the page menu only when the parent match.match/lists record exists" ), f"{MENU_SQL} must insert the page menu only when the parent match catalogue record exists"
assert ( assert (
"WHERE parent.`perms` = 'match.match/lists' AND NOT EXISTS (" in normalized_first_insert "WHERE parent.`type` = 'M' AND parent.`paths` = 'match' AND NOT EXISTS (" in normalized_first_insert
), f"{MENU_SQL} first insert must combine parent binding with AND NOT EXISTS in a single WHERE predicate" ), f"{MENU_SQL} first insert must combine top-level match catalogue binding with AND NOT EXISTS in a single WHERE predicate"
assert ( assert (
"WHERE parent.`perms` = 'match.match/lists' WHERE NOT EXISTS (" not in normalized_first_insert "WHERE parent.`type` = 'M' AND parent.`paths` = 'match' WHERE NOT EXISTS (" not in normalized_first_insert
), f"{MENU_SQL} first insert must not contain a second standalone WHERE NOT EXISTS after the parent WHERE" ), f"{MENU_SQL} first insert must not contain a second standalone WHERE NOT EXISTS after the parent WHERE"
assert normalized_first_insert.count("WHERE") == 2, ( assert normalized_first_insert.count("WHERE") == 2, (
f"{MENU_SQL} first insert should only have the outer WHERE plus the nested WHERE inside NOT EXISTS" f"{MENU_SQL} first insert should only have the outer WHERE plus the nested WHERE inside NOT EXISTS"
) )
assert "UPDATE `la_system_menu` child" in menu_sql, f"{MENU_SQL} must repair existing live menu parent records"
assert "child.`perms` = 'match.matchLive/index'" in normalized_menu_sql, (
f"{MENU_SQL} must target the existing match.matchLive/index menu when repairing the parent node"
)
assert "child.`pid` <> parent.`id`" in normalized_menu_sql, (
f"{MENU_SQL} repair statement must only move the live menu when it is attached to the wrong parent"
)
assert ( assert (
"SELECT (SELECT `id` FROM `la_system_menu` WHERE `perms` = 'match.matchLive/index' LIMIT 1)" in normalized_menu_sql "SELECT (SELECT `id` FROM `la_system_menu` WHERE `perms` = 'match.matchLive/index' LIMIT 1)" in normalized_menu_sql
), f"{MENU_SQL} child permissions must bind to the inserted match.matchLive/index menu" ), f"{MENU_SQL} child permissions must bind to the inserted match.matchLive/index menu"
View File
+15
View File
@@ -0,0 +1,15 @@
> uni-preset-vue@0.0.0 dev:h5
> uni --host 127.0.0.1 --port 5173
请注意运行模式下,因日志输出、sourcemap 以及未压缩源码等原因,性能和包体积,均不及发行模式。
vite是按需编译,运行时点击某个未编译页面会先编译后加载,导致显示较慢,发行后无此问题。
编译器版本:5.07vue3
正在编译中...

vite v5.2.8 dev server running at:

➜ Local: http://127.0.0.1:5173/
ready in 3111ms.
Binary file not shown.

After

Width:  |  Height:  |  Size: 230 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 230 KiB

+29
View File
@@ -13,6 +13,30 @@
## 一、已完成事项 ## 一、已完成事项
### 74. 赛事直播流定时抓取恢复与 Docker 调度优化
- 状态:已完成 - 时间:2026-06-21
完成内容:
- 排查赛事直播线路新增后长期停留在待抓取,确认 `la_dev_crontab` 中的 `crawler match_live_stream` 仅用于管理态记录;服务端 `php think crontab` 对已迁移的 crawler 任务会直接跳过,真实执行入口是测试服 `sport-era-crawler` 容器内由 `docker_task_runner.py` 渲染出的 `/etc/cron.d/sport-era-crawler`
- 确认测试服 `sport-era-crawler` 容器已连续运行 8 天且镜像未重建,旧容器内缺少 `/app/match_live_stream.py`,也没有渲染 `match_live_stream` 的 cron 任务,导致服务器目录虽然已同步源码,但新增线路不会被定时抓取。
- 已在测试服重建并重启 `sport-era-crawler` 容器,确认容器内 `config/crawler_tasks.yaml``/etc/cron.d/sport-era-crawler` 都已包含 `match_live_stream` 每分钟调度。
- 继续排查发现 `docker/crawler/scripts/docker_task_runner.py` 外层已对动作加锁,而 `docker/crawler/match_live_stream.py` 内部再次抢同名 `match_live_stream.lock`,任务会把自己判定成“已在执行中”并直接跳过,表现为 `la_crawler_task_log` 连续成功但 `la_match_live` 数据不更新。
- 优化 `docker_task_runner.py` 在子进程执行时注入 `DQD_RUNNING_UNDER_TASK_RUNNER=1`,并让 `match_live_stream.py` 在该上下文下跳过内部同名锁,保留脚本独立执行时的文件锁能力,避免双重加锁导致的假成功。
- 已补充 `match_live_stream` 回归测试,并在测试服手动触发一轮任务,确认 `la_match_live.play_url_m3u8` 成功写入,`fetch_status=1`,已抓到播放地址的线路会按 10 分钟规则刷新。
涉及模块:
- `docker/crawler/match_live_stream.py`
- `docker/crawler/scripts/docker_task_runner.py`
- `docker/crawler/tests/test_match_live_stream.py`
- `docs/sql/insert_crontab_match_live_stream.sql`
验证记录:
- 已执行 `C:\Python311\python.exe -m unittest docker.crawler.tests.test_match_live_stream.MatchLiveStreamTest.test_run_does_not_skip_due_rows_when_invoked_by_docker_task_runner`,确认修复前红灯场景已被回归测试覆盖。
- 已执行 `C:\Python311\python.exe -m unittest docker.crawler.tests.test_match_live_stream`10 个直播流抓取单测通过。
- 已在测试服执行 `sudo docker compose -f docker-compose.crawler.yml build crawler && sudo docker compose -f docker-compose.crawler.yml up -d --no-deps crawler`,确认新容器已加载 `match_live_stream.py` 与对应 cron。
- 已在测试库补齐 `la_dev_crontab` 中的 `crawler / match_live_stream` 记录,并手动执行 `python /app/scripts/docker_task_runner.py run match_live_stream ...`,确认 `la_match_live``play_url_m3u8` 已成功落库。
### 73. 彩票帖子 AI 分析图片先本地化再识别 ### 73. 彩票帖子 AI 分析图片先本地化再识别
- 状态:已完成 - 时间:2026-06-13 - 状态:已完成 - 时间:2026-06-13
@@ -1499,3 +1523,8 @@
- 注意:2026149-2026150 期因 API 格式变更期间不可回溯(API 只返回当前期),已无法补回。 - 注意:2026149-2026150 期因 API 格式变更期间不可回溯(API 只返回当前期),已无法补回。
涉及模块: 涉及模块:
- `server/public/dongqiudi-crawler/lottery_community.py` - `server/public/dongqiudi-crawler/lottery_community.py`
## 四、避坑记录
- **Docker crawler 新增任务后,不能只同步源码不重建容器**:像 `match_live_stream.py` 这类新增动作,即使服务器目录里已经有文件,运行中的 `sport-era-crawler` 仍可能还是旧镜像;要同时检查容器内 `/app/<task>.py``/app/config/crawler_tasks.yaml``/etc/cron.d/sport-era-crawler` 是否都已加载新任务,否则会出现“代码在服务器上、任务却永远不执行”的假象。
- **Docker 外层 runner 与任务脚本内部不要重复抢同名锁**`docker_task_runner.py` 已经按 action 加过锁,任务脚本内部如果再对同名 action 抢文件锁,会出现“任务日志连续成功、数据库完全没更新”的假成功症状;后续新增 Docker crawler 任务时,要先确认锁只保留一层,或显式区分 runner 锁和脚本独立执行锁。