no message
This commit is contained in:
@@ -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'
|
||||||
|
|||||||
@@ -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`
|
||||||
|
);
|
||||||
@@ -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), \
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
|
||||||
|
> uni-preset-vue@0.0.0 dev:h5
|
||||||
|
> uni --host 127.0.0.1 --port 5173
|
||||||
|
|
||||||
|
请注意运行模式下,因日志输出、sourcemap 以及未压缩源码等原因,性能和包体积,均不及发行模式。
|
||||||
|
vite是按需编译,运行时点击某个未编译页面会先编译后加载,导致显示较慢,发行后无此问题。
|
||||||
|
编译器版本:5.07(vue3)
|
||||||
|
正在编译中...
|
||||||
|
[36m
|
||||||
|
vite v5.2.8[39m[32m dev server running at:
|
||||||
|
[39m
|
||||||
|
[32m➜[39m [1mLocal[22m: [36mhttp://127.0.0.1:[1m5173[22m/[39m
|
||||||
|
|
||||||
|
[36mready in 3111ms.[39m
|
||||||
|
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 230 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 230 KiB |
@@ -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 锁和脚本独立执行锁。
|
||||||
|
|||||||
Reference in New Issue
Block a user