Commit Graph
44 Commits
Author SHA1 Message Date
nyaKazuhaandClaude Fable 5 ab11cc899d feat(official-sync): 下载失败携带类型化错误码
用类型化错误(不做 message 匹配)把资源下载失败归到准确的 BAT-ERR 码:

- curl_transfer:CurlFailureKind::error_code() 映射到网络域码(403→300001、
  404→300002、5xx→300005、dns/connect/timeout/tls/interrupted→30001x、
  proxy→310001、其余→300015);CurlRetryError::final_error_code() 取末次失败的码。
- official_download:新增 pub DownloadError { code, message },From<String>→internal,
  Display/Error;pull_* 的返回类型由 String 改为 DownloadError,内部 String 错误经
  From 归 internal,下载失败边界带 pull_one 的 error_code(),非官方 URL 带
  non_official_url(300030)。
- official_update:pull 错误改用 anyhow::Error::new 保留 DownloadError 类型。
- 任务 worker:downcast DownloadError 取 code 写入任务记录的 error(失败不再一律
  900001),非下载失败仍归 internal。

验证:新增 kind→code 映射单测;pull 404 测试断言 DownloadError.code()==HTTP_NOT_FOUND;
lib/core 全量测试通过,clippy --all-targets 干净。

范围说明:本次覆盖资源下载路径;launcher/metadata 拉取(official_launcher)的网络
失败仍归 internal,可按同一模式后续接入。

对应 issue #1(错误码细分映射,第一批)。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 07:01:31 -07:00
nyaKazuhaandClaude Fable 5 9a5fef23af feat(daemon): 补全 task.cancel 与 task.logs
完成 task.* 命名空间:

- task.cancel:每个任务带共享的取消标志(Arc<AtomicBool>),worker 的 should_cancel
  同时检查它和 daemon 停止标志;请求取消后置标志,worker 在下一个同步检查点中止,
  任务转为 cancelled(协作式,不硬杀正在执行的 curl)。已结束任务返回 already-finished,
  不存在返回 700004。
- task.logs:worker 把每条 progress 追加到任务的有界日志(上限 200 行),task.logs
  返回该历史;不存在返回 700004。TaskRecord 的 cancel/log 字段 serde(skip),不进
  task.status 输出。
- 新增 cancelled 任务状态;prune 与已结束判定纳入 cancelled。
- USERGUIDE RPC 方法表更新(cancel/logs 标为已实现)。

验证:新增取消/日志注册表单测(63 bin 测试全过);真机端到端确认触发 resource.sync
后 task.cancel 使任务转为 cancelled、task.logs 返回进度历史。

对应 issue #1(task.* 命名空间补全)。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 06:00:26 -07:00
nyaKazuhaandClaude Fable 5 01ea5d8fa5 feat(daemon): 任务执行器 + 异步 resource.sync/verify(阶段 1b)
在 daemon 里加入任务模型,让 resource.sync/verify 经 RPC 触发即返回 task_id、
可轮询进度与最终结果:

- 专用任务 worker 线程 + FIFO 队列(mpsc);TaskRegistry 句柄包住内存态注册表
  (HashMap,有界保留 64 条已结束任务,运行中/排队中不裁剪),方法访问便于将来
  换 Redis 等持久化后端。
- 进程内 sync 锁:watch 循环与 worker 在跑同步前都获取它,互相等待而非撞
  .official-sync.lock 文件锁失败;只在实际跑同步的那段持锁,空闲睡眠不持锁。
- resource.sync/resource.verify 入队返回 { task_id, kind }(envelope status=accepted);
  worker 复用 run_with_progress_and_cancellation(sync 用 daemon config + force;
  verify 用 dry-run+plan+audit),进度写入任务记录,should_cancel 接 daemon 停止标志
  (停机中止在途任务)。task.status/task.list 从注册表读真实数据。

验证:新增任务分发/入队/注册表单测(62 bin 测试全过);真机端到端确认经裸 socket
触发 resource.sync 返回 task_id、轮询 task.status 走到最终态(fake curl 失败→
failed 且记录 error)、task.list 可见、resource.verify 亦入队。满足 issue #1 验收
「至少一个同步任务可经 RPC 触发并经 task.status 查询进度和最终结果」。

任务仅存内存(随 daemon 生死);task.cancel/logs、错误码细分映射留待后续。

对应 issue #1(任务模型 + 最小方法集打通)。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 05:23:24 -07:00
nyaKazuhaandClaude Fable 5 b95a3ac417 feat(daemon): RPC 统一 envelope、命名空间分发与只读方法
阶段 1a 的协议地基(承接错误码模型):

- 统一 envelope:所有响应装入 JSON-RPC 2.0 result 的
  { ok, status, data, error, request_id };error 用 bat_core::ApiError
  (含 BAT-ERR 码/kind/domain/location/retryable)。传输层解析失败仍走
  JSON-RPC 顶层 error。request_id 为 req-<pid>-<seq>。
- 命名空间分发:方法改用国际惯例 <namespace>.<action>;daemon.* 为规范名,
  bat.* 保留为兼容别名(canonical_rpc_method 解析)。未知方法 → 700001,
  已规划未实现(resource.sync/verify、catalog.*/patch.*/unityfs.* 等)→ 700003。
- 只读方法:daemon.status/logs/stop/reload/refresh(迁移)、resource.state(新增,
  返回资源发布根 + 版本状态 + 上次同步结果)、task.status/task.list(任务库暂空,
  status → 700004,list → 空列表;执行器留待阶段 1b)。
- 客户端 daemon_rpc_call 解包 envelope(检查 ok、返回 data、把 error 转 Err),
  CLI 调用方零改动。

验证:新增分发/别名/envelope 单测(60 bin 测试全过);真机起停 daemon 端到端确认
daemon.status/logs/resource.state/task.list 经 CLI 与裸 socket 均正确返回 envelope,
bat.* 别名生效,未知/未实现返回对应 BAT-ERR 码。

对应 issue #1(协议基础设施 + 最小只读方法集)。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 04:19:22 -07:00
nyaKazuhaandClaude Fable 5 232d74b617 fix(daemon): 串行化 bat-status.json 的读-改-写
watch 循环线程(update_daemon_status/update_daemon_progress)与 RPC 处理线程
(update_daemon_state_only)都会对状态文件做“读→改字段→写回”。单次写虽原子,
但整段读-改-写非原子,无互斥时并发线程会 last-writer-wins 丢失对方的字段更新。

新增进程级 DAEMON_STATUS_FILE_LOCK,三处读-改-写函数入口统一获取该锁串行化
(锁中毒时取 into_inner 继续,不 panic)。

对应 issue #18 维护清单 3-4。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 09:38:46 -07:00
nyaKazuhaandClaude Fable 5 0656ad05c7 refactor(official-sync): CLI 工程卫生清理
- doctor daemon_status 检查(3-3):ok 与 message 原分别取自解析结果和文件存在性,
  文件存在但解析失败时会“ok=false 却提示可解析”。改为从同一次解析结果派生两者。
- localize_error_message(3-1a):空壳直通占位函数,删除并在 4 处调用点直接用消息。
- daemon 路径 helper 形参(3-1b):output_root 实际语义是 state_dir,统一改名消除误导。
- stop_daemon 非 RPC 路径(3-1c):删除无意义的 let-else(_pid 读后丢弃、两分支
  逐字重复 stop_daemon_inner+print_report),stop_daemon_inner 内已做 PID 校验。
- parse_http_status(3-1d):改为锚定 curl stderr 中 "error" 之后再取三位状态码,
  避免误取端口/字节数等无关三位数字;新增锚定测试。
- 非 Unix process_exists(3-1e):补注释说明保守返回 true 的理由(无法探测存活,
  避免误回收可能仍在运行的 daemon)。

对应 issue #18 维护清单 3-1、3-3。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 09:35:31 -07:00
nyaKazuhaandClaude Fable 5 28e9ce5a6f refactor(official-sync): 删除误导性的 skipped_this_run 字段并修正措辞
quarantine 条目的 skipped_this_run 字段恒写 true、仅测试读取、不参与任何逻辑,
且名称误导——下载失败是 fail-fast 中止整轮,并非“跳过该 URL 继续”。删除该字段,
并把失败消息中的“跳过本轮”改为“中止本轮同步、不发布不完整资源”以消除歧义。
(quarantine 为瞬时状态文件且未启用 deny_unknown_fields,旧文件多出的键会被忽略。)

对应 issue #18 维护清单 3-2。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 09:21:16 -07:00
nyaKazuhaandClaude Fable 5 48ffc08cdf fix(cas): exists 返回 Result 以区分不存在与存储故障
CasRepository::exists(core trait)与底层 Storage::exists 原返回裸 bool,
FileSystemStorage 用 try_exists(...).unwrap_or(false)、infra 适配层吞掉解析/
初始化错误返回 false,调用方无法区分“对象不存在”和权限/IO/初始化故障。

将整条链改为 Result<bool>:
- storage trait 与 FileSystemStorage:try_exists 的 IO 错误经 ? 传播;
- repository FileSystemCasRepository::exists 及 store/add_reference 内部调用;
- core CasRepository::exists trait;
- infra 适配层:解析失败→InvalidArgument、初始化/引擎错误经 map_error 传播。
Ok(false) 仅表示确实不存在。

新增单测:合法但不存在的对象返回 Ok(false),非法 ObjectId 返回 Err(而非静默
false);同步更新各层 exists 断言。

对应 issue #18 维护清单 2-1。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 08:53:54 -07:00
nyaKazuhaandClaude Fable 5 c362fd80f4 fix(import): 导入失败回滚,find_bundle 拒绝同名歧义
- 非事务导入:import_manifest_bundles 中途某条目失败(如 AssetBundle 解析失败)
  时,之前已写入的 CAS 对象和 ResourceRepository 行不回滚,留下部分导入状态。
  改为记录本次写入的 object_id 与资源 id,任一条目失败时 best-effort 回滚
  (删除资源行、对 store 增加的引用逐一 remove_reference)。
- find_bundle 跨目录同名:文件名回退无目录约束,jp/data.bundle 与 en/data.bundle
  同名时可能静默取错数据。改为返回 Result:精确路径优先,文件名回退仅在唯一匹配
  时采用,多个同名 bundle 时显式报错而非猜测。

新增单测:后续条目失败时回滚先前成功条目(资源行清空、CAS 引用回滚为 0)、
同名 bundle 歧义显式报错。

对应 issue #18 维护清单 2-2。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 08:29:27 -07:00
nyaKazuhaandClaude Fable 5 b570f37359 fix(official-sync): 区分代理故障并前置校验代理配置
- 代理故障分类:curl 因代理 407 认证失败(CONNECT 隧道失败)、无法解析/连接
  代理等,原被归为普通可重试网络错误,永不成功也会耗尽 per-URL 重试且无诊断。
  新增 CurlFailureKind::Proxy,从 curl stderr 识别代理故障,per-URL 层 fail-fast
  不重试(daemon error-retry 循环仍整体重试以覆盖代理短暂抖动),并在失败摘要里
  以 kind=proxy 明确标注。
- 前置校验:parse_proxy_config 新增 validate_proxy_url,拒绝拼错的代理 scheme
  (如 htp://),只接受 http/https/socks4/socks4a/socks5/socks5h 或无 scheme
  的 host:port。
- doctor:proxy 检查不再恒 ok,对解析出的代理 URL(含 auto 从环境变量取得的)
  做 scheme 校验,前置暴露配置错误。

新增单测:代理 407/解析失败归为 Proxy 且不重试、源站连接失败仍为 Connect 可重试、
拒绝不支持的 scheme、无 scheme host:port 接受。

对应 issue #18 维护清单 2-5。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 08:18:37 -07:00
nyaKazuhaandClaude Fable 5 f0fbe65429 fix(official-sync): 复用 staging 异常时退化为全量重下而非中止
recoverable_staging_path 对「staging 存在但不可用」(被外部替换成文件/symlink、
路径组件含 symlink、id 不匹配、路径逃逸等)一律返回 Err,经调用方 ? 中止整轮
同步。失败记录残留时每轮都会撞到同一异常而永久失败、无法自愈。

改为返回 Option<PathBuf>:任何无法安全复用的情况返回 None,走全量重下(始终安全
的 fail-closed 回退,复用文件仍逐一校验,不会发布损坏内容)。新增单测:staging
被替换成普通文件时不复用、返回 None 而非 Err。

对应 issue #18 维护清单 2-6。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 08:08:12 -07:00
nyaKazuhaandClaude Fable 5 42d69dd4bd fix(official-sync): 拒绝携带 query/fragment 的资源 URL
destination_for_url 原来对每个路径片段静默剥除 query/fragment,导致仅 query
不同的两个官方 URL 映射到同一目标文件而相互覆盖,并使每轮 hash 复用校验失配、
反复重下。官方资源 URL 本不携带 query(发现 URL 由固定拼接生成),改为直接拒绝,
消除该碰撞路径。新增单测。

对应 issue #18 维护清单 2-3。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 08:03:40 -07:00
nyaKazuhaandClaude Fable 5 3cb3637749 fix(official-sync): 清理未被引用的孤儿 staging 目录
同版本失败 staging 复用已由 060b7d7 处理,但跨版本失败、或被 trim/去重挤出
failed_versions 记录后残留的 .staging/<id> 目录永不被复用也无人清理,长期 daemon
场景下多 GB 孤儿目录会累积耗尽磁盘。

新增 gc_orphan_staging:枚举 .staging 下子目录,只保留 in_progress_version 与
failed_versions 引用的 <id>,删除其余(跳过普通文件与 symlink,staging 根不存在
时无操作)。在同步成功发布后(读取最新版本状态)和 clean-stable(后台已停止)两处
调用;GC 失败仅告警,不影响发布结果。

对应 issue #18 维护清单 1-4。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 07:29:37 -07:00
nyaKazuhaandClaude Fable 5 4d818f512c fix(official-sync): 发布成功后立即提交版本状态事务
publish() 切换 current symlink 后,写最终 snapshot 或 complete_version_state
若失败会经 ? 提前返回,此时 VersionStateGuard 尚未 commit,其 Drop 会把这个
磁盘上已发布(current 已指向)的版本记入 failed_versions,造成状态与磁盘不一致。

改为在 publish() 成功后立即取出完成记录并 commit 版本状态事务,随后 snapshot
与 version-state 写入失败降级为可下轮重试的警告(不再 ? 中断、不再误记 failed);
snapshot_written 也改为仅在实际写入成功时置 Some。

对应 issue #18 维护清单 1-3。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 07:17:51 -07:00
nyaKazuhaandClaude Fable 5 565fd317de fix(official-sync): curl 代理改由环境变量传入,堵住 argv 泄漏
apply_curl_proxy 原来对 URL 型代理执行 command.arg("--proxy").arg(url),
把可能含凭据的代理 URL 放进 curl 子进程 argv,会出现在 curl 进程的
/proc/<pid>/cmdline(0444,世界可读,每次下载存活期间可被本地任意用户读到)。

改为先 remove_proxy_env 清除所有继承的代理变量,再通过 ALL_PROXY/all_proxy
环境变量把代理传给 curl(大小写两种写法都设,避免 curl 只读其一)。凭据从
世界可读的 cmdline 转移到仅属主可读的 /proc/<pid>/environ(0400)。no_proxy
列表不含凭据,仍以 --noproxy 参数传入;disabled 模式保持 --noproxy * 直连。

至此代理凭据泄漏面全部堵住:daemon 子进程 argv、0600 状态文件(55b9bab),
以及本次的 curl 孙进程 argv。

验证:
- 新增单测用 Command::get_args/get_envs 断言 URL 只进 ALL_PROXY 环境变量、
  不进 argv,且 scheme 专用变量被清除;disabled 模式强制直连。
- 真机经本地混合代理 127.0.0.1:7897 端到端验证:curl shim 记录 11 次调用,
  凭据 0 次进 argv、0 个 --proxy 参数,代理仅在 ALL_PROXY 环境变量中;curl
  确实经该代理到达官方 launcher/CDN/resources.assets 端点,功能未回归。
- 对抗性审查确认 auto/disabled/no_proxy 语义与旧 --proxy 等价,env 先删后设
  最终为 set,无其它遗漏的凭据落 argv/日志/状态文件路径。

对应 issue #18 维护清单 1-2 残留项(curl 子进程)。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 06:44:03 -07:00
nyaKazuhaandClaude Fable 5 55b9babb66 fix(official-sync): 代理凭据改由环境变量下传子进程
彻底收敛 1-2 的两处残留凭据暴露:

- 子进程 argv:daemon_child_args 不再把 --proxy <url> 写入子进程参数,改为
  只放不含凭据的内部 flag --proxy-from-env,真实 URL 经 BAT_OFFICIAL_SYNC_PROXY_URL
  环境变量下传。凭据从世界可读的 /proc/<pid>/cmdline(0444)转移到仅属主可读的
  /proc/<pid>/environ(0400);子进程解析后立即 env::remove_var,避免被 curl 等
  孙进程继承。
- 状态文件 at-rest:0600 状态文件的 command 字段不再含凭据。restart/reload 复用
  所需的 URL 改存专用 0600 文件 bat-proxy.secret,该文件永不进入状态输出或 --json;
  clean-stable 会在后台停止后清除它。

复用型 restart/reload 从 bat-proxy.secret 还原代理;凭据文件缺失时(如 clean-stable
后)明确报错要求重新传入 --proxy,而非静默丢弃代理。

验证:新增 curl_proxy_url、secret 文件 0600 往返、--proxy-from-env 读取并清除
环境变量的单测;真机起停 daemon 确认凭据不在 cmdline/状态文件/status --json,
仅在 0600 secret 文件与 owner-only environ;复用 restart 成功还原、clean-stable
后复用 restart 明确报错。

对应 issue #18 维护清单 1-2 残留项。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 05:20:22 -07:00
nyaKazuhaandClaude Fable 5 810b39c2cd fix(official-sync): 修复阻塞 clippy 门禁的两处告警
cargo clippy --workspace -- -D warnings 在 HEAD 上失败,阻塞项目验证门禁:
- resolve_bootstrap 因 c123f6c 新增 curl_proxy 参数达到 8 个,触发
  too_many_arguments。将 curl_command/curl_proxy/unzip_command 收拢为
  BootstrapTools 结构体,降到 6 个参数。
- visible_historical_failed_versions(169822a 引入)的显式生命周期可省略,
  触发 needless_lifetimes。按 clippy 建议省略。

对应 issue #18 维护清单 1-7。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 04:40:35 -07:00
nyaKazuhaandClaude Fable 5 eef8c52497 fix(official-sync): 脱敏代理凭据避免泄漏到日志与状态输出
两处代理凭据泄漏:
- redact_proxy_url 对无 scheme 的代理串(user:secret@host:port,curl 默认按
  http 处理)完全不脱敏,明文凭据会进入 progress 事件、daemon 日志和
  bat-events.jsonl。现对无 scheme 但含 userinfo 的情况一并脱敏。
- daemon 保存的 command 含原始代理 URL,经 RPC status 和 bat status --json
  回显到 stdout(会被监控采集)。新增 redact_command_proxy_credentials,在
  build_daemon_status_report 的对外拷贝里脱敏 --proxy 值;磁盘状态文件
  (0600)仍保留原始 command 供 restart/reload 复用。

残留:子进程 argv 仍可经 /proc/<pid>/cmdline 读到代理凭据,以及 0600 状态
文件内的 at-rest 凭据,二者受进程属主与文件权限约束,后续可改由环境变量下
传子进程进一步收敛。

对应 issue #18 维护清单 1-2。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 04:31:10 -07:00
nyaKazuha 060b7d73e5 fix(official-sync): 复用失败 staging 避免重复全量下载 2026-07-16 00:35:24 +08:00
nyaKazuha c123f6c0dc feat(official-sync): 支持本地代理下载官方资源 2026-07-16 00:12:27 +08:00
nyaKazuha 169822abaf fix(daemon): 区分当前下载与历史失败状态 2026-07-15 23:22:30 +08:00
nyaKazuha 5b31112e91 fix(daemon): 使用人类格式输出 RPC 下载进度 2026-07-15 23:05:28 +08:00
nyaKazuha 7890b667d7 fix(daemon): 去重失败版本并优化状态输出 2026-07-15 22:32:27 +08:00
nyaKazuha 60b8339ecb fix(official): 修正官方 hash 校验算法 2026-07-15 22:30:33 +08:00
nyaKazuha 90307a3243 feat: track official versions and import resources
Persist official version-state, extend CAS/ResourceRepository import classification, and add offline catalog and failure regression fixtures.

Fixes #13

Fixes #12

Fixes #8
2026-07-15 19:53:10 +08:00
nyaKazuha 4192d7ee4c fix: classify official download failures
Distinguish terminal HTTP errors from retryable CDN/network failures, record quarantined resources, and report failed download progress.

Fixes #7
2026-07-14 01:30:12 +08:00
nyaKazuha 25c2c4d40f feat: add official full pull smoke
Emit runtime download progress and validation summaries for official sync.

Add repeatable real-network full pull smoke runbook and script.

Fixes #4

Fixes #6
2026-07-14 00:48:59 +08:00
nyaKazuha 2ff99ae9e3 feat: publish official resources atomically
Fixes #9

Fixes #11
2026-07-14 00:10:39 +08:00
nyaKazuha 45aba2fb2b fix: harden official sync filesystem boundaries
Fixes #14
2026-07-13 23:17:52 +08:00
nyaKazuha 599ead8391 fix: serialize daemon control commands
Fixes #10
2026-07-13 22:02:11 +08:00
nyaKazuha 1bcb9c828a feat: summarize official validation results
Fixes #5
2026-07-13 01:06:37 +08:00
nyaKazuha e5fcf26e19 feat: validate official zip structure 2026-07-12 23:37:47 +08:00
nyaKazuha f7255f7c04 feat: improve bat daemon cli output 2026-07-12 23:04:13 +08:00
nyaKazuha 264e78c440 feat: harden official sync scheduling and integrity 2026-07-11 01:06:02 +08:00
nyaKazuha 2bdc55a1be feat: support cancellable official sync 2026-07-10 00:36:40 +08:00
nyaKazuha f704ec18c5 feat: add official sync startup banner 2026-07-07 23:00:02 +08:00
nyaKazuha ea0920e4e3 feat: add official sync progress logging 2026-07-07 22:13:07 +08:00
nyaKazuha b2d6871926 fix: harden official resource sync retries 2026-07-07 01:14:54 +08:00
nyaKazuha e0d43fb994 fix official auto discovery for current launcher manifest 2026-07-06 23:02:52 +08:00
nyaKazuha 789402c887 feat: add official resource sync pipeline
Add the production-facing official update service and bat-official-sync watch CLI for unattended resource synchronization.

Support launcher-resource discovery without installing the launcher, remote marker snapshots, local manifest audit and repair, official seed hash validation, bootstrap caching, richer Addressables coverage, SQLite resource persistence, and FFI JSON helpers.
2026-07-05 23:49:56 +08:00
nyaKazuha adc1cd5b91 feat: prepare experiment push package 2026-06-30 00:08:36 +08:00
nyaKazuha 3c00659691 refactor: reduce quality findings 2026-06-28 15:42:12 +08:00
nyaKazuha b202d7b231 feat: implement production cas v1 2026-06-28 12:39:17 +08:00
nyaKazuha dd53e3054e chore: establish development baseline 2026-06-28 01:27:09 +08:00