增加Skill每日自动更新和使用记录

This commit is contained in:
chen qiang
2026-09-30 11:42:45 +08:00
parent a314c64b0b
commit 209d27de59
9 changed files with 351 additions and 2 deletions
+3
View File
@@ -3,3 +3,6 @@ __pycache__/
outputs/ outputs/
scripts/config.json scripts/config.json
scripts/*.token scripts/*.token
scripts/update-state.json
scripts/update-state.json.*
scripts/update-state.lock
+2
View File
@@ -1,5 +1,7 @@
# oracle-jump-query Skill Release Standard # oracle-jump-query Skill Release Standard
Release 1.5.51: CLI 每次启动记录最后使用时间,按本机日期每日首次使用自动抓取 origin/main 并仅快进安装,成功后重新启动当前命令;本机状态与并发锁不提交 Git。保留本机配置,源码修改、暂存修改、分支分叉或配置冲突时暂停安装;失败不阻止业务命令。本次新增本机更新逻辑,不修改 Agent、transit-server、HTTP 接口、登录配置或审计 action、字段及保存策略,命令数保持 32。发布时同步私有主库、安装目录和无私有历史公共镜像。
取号 CODE 定义在 `AD_SEQUENCE` 表;1.5.50 的编号规则分析路径为 `DOCNO -> AD_COLUMN.SEQUENCENAME -> AD_SEQUENCE`。 取号 CODE 定义在 `AD_SEQUENCE` 表;1.5.50 的编号规则分析路径为 `DOCNO -> AD_COLUMN.SEQUENCENAME -> AD_SEQUENCE`。
Release 1.5.50: 明确取单号函数 `Get_SequenceNo('<CODE>', AD_CLIENT_ID)`,默认 `CODE` 取单据 `DOCNO` 字段对应的 `AD_COLUMN.SEQUENCENAME`,客户 ID 使用单据实际值;取号仅生成 SQL 示例,不通过查询通道调用。本次仅更新 Skill 文档,不修改 Agent、transit-server、CLI/HTTP 接口、配置或审计 action、字段及保存策略;命令数保持 32。发布时同步私有主库、安装目录和无私有历史公共镜像。 Release 1.5.50: 明确取单号函数 `Get_SequenceNo('<CODE>', AD_CLIENT_ID)`,默认 `CODE` 取单据 `DOCNO` 字段对应的 `AD_COLUMN.SEQUENCENAME`,客户 ID 使用单据实际值;取号仅生成 SQL 示例,不通过查询通道调用。本次仅更新 Skill 文档,不修改 Agent、transit-server、CLI/HTTP 接口、配置或审计 action、字段及保存策略;命令数保持 32。发布时同步私有主库、安装目录和无私有历史公共镜像。
+6
View File
@@ -1,3 +1,9 @@
## 1.5.51 (2026-09-30)
- CLI 每次启动记录最后使用时间;按本机日期,每个使用日首次启动自动抓取 `origin/main`,仅快进安装更新并重新启动当前命令,同日不重复检测。
- 使用独立本机状态文件和并发锁,保留本机配置;源码修改、暂存修改、分支分叉或配置冲突时暂停安装,抓取失败和超时继续业务命令,更新提示不污染 JSON 输出。
- 本次新增 Skill 本地更新逻辑,不修改 Agent、transit-server、HTTP 接口、登录配置格式或审计 action、字段及保存策略;CLI 命令数保持 32。使用状态仅本机保存,不写入中转审计库。
## 1.5.50 (2026-09-30) ## 1.5.50 (2026-09-30)
- 明确取单号函数 `Get_SequenceNo('<CODE>', AD_CLIENT_ID)`,默认 `CODE` 来自该单据 `DOCNO` 字段对应的 `AD_COLUMN.SEQUENCENAME`;客户 ID 使用单据实际值,不固定为 `37`。 - 明确取单号函数 `Get_SequenceNo('<CODE>', AD_CLIENT_ID)`,默认 `CODE` 来自该单据 `DOCNO` 字段对应的 `AD_COLUMN.SEQUENCENAME`;客户 ID 使用单据实际值,不固定为 `37`。
+9
View File
@@ -306,6 +306,7 @@ python scripts/oracle_skill.py ops_report HENLO
- 如果表存在 `DOCNO` 字段,先到 `AD_COLUMN` 查询该字段的 `SEQUENCENAME`。 - 如果表存在 `DOCNO` 字段,先到 `AD_COLUMN` 查询该字段的 `SEQUENCENAME`。
- 取单号函数为 `Get_SequenceNo('<CODE>', AD_CLIENT_ID)`;默认 `CODE` 取该单据 `DOCNO` 字段对应的 `AD_COLUMN.SEQUENCENAME` 值,第二个参数取单据实际的 `AD_CLIENT_ID`,确认是 `37` 时才写 `37`。配置为空时不猜测 `CODE`,特殊业务按实际配置和过程逻辑核实;只生成 SQL 示例,不通过查询通道调用取号函数。 - 取单号函数为 `Get_SequenceNo('<CODE>', AD_CLIENT_ID)`;默认 `CODE` 取该单据 `DOCNO` 字段对应的 `AD_COLUMN.SEQUENCENAME` 值,第二个参数取单据实际的 `AD_CLIENT_ID`,确认是 `37` 时才写 `37`。配置为空时不猜测 `CODE`,特殊业务按实际配置和过程逻辑核实;只生成 SQL 示例,不通过查询通道调用取号函数。
- 取号 `CODE` 的定义在 `AD_SEQUENCE` 表,分析编号规则时沿 `DOCNO -> AD_COLUMN.SEQUENCENAME -> AD_SEQUENCE` 核实。 - 取号 `CODE` 的定义在 `AD_SEQUENCE` 表,分析编号规则时沿 `DOCNO -> AD_COLUMN.SEQUENCENAME -> AD_SEQUENCE` 核实。
- 如果表存在 `AD_CLIENT_ID` 字段,默认值为 `37`。 - 如果表存在 `AD_CLIENT_ID` 字段,默认值为 `37`。
- 如果表存在 `AD_ORG_ID` 字段,默认值为 `27`。 - 如果表存在 `AD_ORG_ID` 字段,默认值为 `27`。
- 子表 `AD_CLIENT_ID`、`AD_ORG_ID` 默认继承父表对应字段值。 - 子表 `AD_CLIENT_ID`、`AD_ORG_ID` 默认继承父表对应字段值。
@@ -353,6 +354,14 @@ python scripts/oracle_skill.py version agent --all --json
## 从 Git 更新 Skill ## 从 Git 更新 Skill
### 每日自动更新与使用记录
CLI 启动时记录最后使用时间;按运行电脑本地日期,每个使用日首次启动自动 `fetch origin main`,检测到新版本时仅在 `main` 分支执行快进安装,并重新启动当前命令使用新版本。同一天后续启动不重复抓取;未使用的日期不运行后台任务。每次开始应用 Skill 时先运行 `python scripts/oracle_skill.py capabilities --json`,使只参考业务知识的任务也触发检测。
本机状态文件为 `scripts/update-state.json`:`last_used_at` 记录最后使用时间,`last_checked_at` / `last_check_date` 记录检测时间和日期,`status` 记录 `checking`、`up_to_date`、`updated`、`update_blocked`、`check_failed` 或 `not_git`;检测完成时可包含本机和远端提交 ID。状态、锁和临时文件不提交 Git,不保存 token、密钥、SQL 或查询结果。
自动更新保留本机 `config.json` 和 `scripts/config.json`;源码有未提交修改、存在暂存修改、分支分叉或配置冲突时暂停安装并继续原命令。抓取超时为 10 秒,认证和网络失败不阻止业务命令,也不在当天每次调用时重试;可手动执行 `git pull --ff-only origin main` 重试。提示写入 stderr,stdout 的 JSON 输出不受影响。Git checkout 使用自己的 `origin`,私有和公共安装均可使用;无 `.git` 的下载目录只记录使用时间。
Skill 仅通过 Gitea Git 仓库更新,不使用 OSS 或 `skill_update` 命令。源码仓库完成修改并推送私有库后,必须同步更新无历史公共镜像;随后在已安装目录执行: Skill 仅通过 Gitea Git 仓库更新,不使用 OSS 或 `skill_update` 命令。源码仓库完成修改并推送私有库后,必须同步更新无历史公共镜像;随后在已安装目录执行:
```powershell ```powershell
+5 -1
View File
@@ -5,10 +5,12 @@ description: Oracle 跳板查询技能。通过中转服务查询远程 Oracle
# Oracle 跳板查询 # Oracle 跳板查询
> **版本:v1.5.50** · [更新日志](./CHANGELOG.md) > **版本:v1.5.51** · [更新日志](./CHANGELOG.md)
## 使用原则 ## 使用原则
每次开始使用本 Skill(包括仅参考业务知识)时,先执行 `python scripts/oracle_skill.py capabilities --json`。CLI 每次启动记录最后使用时间;按运行电脑本地日期,当天第一次启动自动从当前 `origin/main` 抓取更新并尝试快进安装,成功后自动重新启动当前命令。无需登录即可检测;当天后续启动只记录使用时间。没有使用的日期不启动后台任务。
使用本 skill 时,先确认当前登录状态和 client。用户指定“恒诺 / 品小二 / 未芮 / 康奈”等服务器时,可以用 `switch <clientCode|clientName>` 按 code 或名称切换;找不到目标服务器时,skill 会先刷新 client 列表再重试匹配。若名称不够准确并匹配到多个 client,只列出候选项,请用户指定更准确的 client code,不要直接猜测切换。 使用本 skill 时,先确认当前登录状态和 client。用户指定“恒诺 / 品小二 / 未芮 / 康奈”等服务器时,可以用 `switch <clientCode|clientName>` 按 code 或名称切换;找不到目标服务器时,skill 会先刷新 client 列表再重试匹配。若名称不够准确并匹配到多个 client,只列出候选项,请用户指定更准确的 client code,不要直接猜测切换。
本 skill 的数据库通道默认只读。可以生成 SQL 和说明,但不得通过 `query` / `qperm` 执行写库语句,包括 `INSERT`、`UPDATE`、`DELETE`、`MERGE`、`DDL`、提交存储过程和其他会改变业务数据的调用。用户要求写库时,只生成 SQL,并明确说明未执行。 本 skill 的数据库通道默认只读。可以生成 SQL 和说明,但不得通过 `query` / `qperm` 执行写库语句,包括 `INSERT`、`UPDATE`、`DELETE`、`MERGE`、`DDL`、提交存储过程和其他会改变业务数据的调用。用户要求写库时,只生成 SQL,并明确说明未执行。
@@ -216,6 +218,8 @@ python scripts/oracle_skill.py sys_functions BOSNDS3
## Skill 更新方式 ## Skill 更新方式
每日检测和使用记录保存在本机 `scripts/update-state.json`,包含 `last_used_at`、`last_checked_at`、`last_check_date`、`status` 及检测到的提交 ID;文件和并发锁均不提交 Git,不保存登录凭证或查询内容。检测失败仍记为当天已尝试,下次使用日再自动检测;可随时执行下面的手动拉取命令重试。自动安装只允许 `main` 分支快进:源码有未提交修改、存在暂存修改、分支分叉或配置冲突时保留本机文件并继续原命令;本机 `config.json`、`scripts/config.json` 保留原内容。网络抓取设有 10 秒超时,提示只输出到 stderr,`--json` 的 stdout 保持可解析。没有 `.git` 的下载目录仍记录使用时间,但需先安装为 Git checkout 才能自动更新。
Skill 仅通过 Gitea Git 仓库更新,不使用 OSS 发布或 `skill_update` 命令。源码修改、提交和推送均在源码仓库完成;私有库推送后必须同步无历史公共镜像,已安装目录仅执行拉取与验证: Skill 仅通过 Gitea Git 仓库更新,不使用 OSS 发布或 `skill_update` 命令。源码修改、提交和推送均在源码仓库完成;私有库推送后必须同步无历史公共镜像,已安装目录仅执行拉取与验证:
```powershell ```powershell
+1 -1
View File
@@ -1 +1 @@
1.5.50 1.5.51
+3
View File
@@ -3823,4 +3823,7 @@ def main():
if __name__ == "__main__": if __name__ == "__main__":
from skill_update import on_use
if on_use(SKILL_DIR):
os.execv(sys.executable, [sys.executable] + sys.argv)
main() main()
+162
View File
@@ -0,0 +1,162 @@
"""Daily Git update checks and local usage timestamps, independent of login config."""
import contextlib
import json
import os
import subprocess
import sys
import tempfile
from datetime import datetime
from pathlib import Path
GIT_TIMEOUT = 10
@contextlib.contextmanager
def _state_lock(path):
path.parent.mkdir(parents=True, exist_ok=True)
with path.open("a+b") as handle:
if os.name == "nt":
import msvcrt
handle.seek(0, os.SEEK_END)
if not handle.tell():
handle.write(b"0")
handle.flush()
handle.seek(0)
msvcrt.locking(handle.fileno(), msvcrt.LK_LOCK, 1)
try:
yield
finally:
handle.seek(0)
msvcrt.locking(handle.fileno(), msvcrt.LK_UNLCK, 1)
else:
import fcntl
fcntl.flock(handle.fileno(), fcntl.LOCK_EX)
try:
yield
finally:
fcntl.flock(handle.fileno(), fcntl.LOCK_UN)
def _read_state(path):
try:
with path.open(encoding="utf-8") as handle:
state = json.load(handle)
return state if isinstance(state, dict) else {}
except (OSError, ValueError):
return {}
def _write_state(path, state):
fd, temporary = tempfile.mkstemp(prefix=path.name + ".", dir=str(path.parent))
try:
with os.fdopen(fd, "w", encoding="utf-8") as handle:
json.dump(state, handle, ensure_ascii=False, indent=2)
handle.write("\n")
os.replace(temporary, str(path))
finally:
if os.path.exists(temporary):
os.unlink(temporary)
def _git(root, *args):
env = os.environ.copy()
env["GIT_TERMINAL_PROMPT"] = "0"
env["GCM_INTERACTIVE"] = "never"
result = subprocess.run(
["git", "-C", str(root), *args], stdout=subprocess.PIPE,
stderr=subprocess.PIPE, timeout=GIT_TIMEOUT, env=env,
)
if result.returncode:
# Git output can contain authenticated remote URLs; never store or print it.
raise RuntimeError("git command failed")
return result.stdout.decode("utf-8", errors="replace").strip()
def _check_remote(root):
# A downloaded directory may sit inside an unrelated parent repository.
if not (root / ".git").exists():
return {"status": "not_git"}
_git(root, "fetch", "--quiet", "origin", "main")
local = _git(root, "rev-parse", "HEAD")
remote = _git(root, "rev-parse", "FETCH_HEAD")
outcome = {
"status": "up_to_date" if local == remote else "update_available",
"local_commit": local,
"remote_commit": remote,
}
if local == remote:
return outcome
if _git(root, "rev-parse", "--abbrev-ref", "HEAD") != "main":
outcome["status"] = "update_blocked"
return outcome
changed = _git(root, "diff", "--name-only").splitlines()
if any(name not in ("config.json", "scripts/config.json") for name in changed) or _git(root, "diff", "--cached", "--name-only"):
outcome["status"] = "update_blocked"
return outcome
try:
_git(root, "merge-base", "--is-ancestor", local, remote)
except RuntimeError:
outcome["status"] = "update_blocked"
return outcome
# Keep configuration bytes local, including clean tracked template config.
saved = {}
for name in ("config.json", "scripts/config.json"):
path = root / name
if path.exists():
saved[path] = path.read_bytes()
try:
_git(root, "merge", "--ff-only", "--quiet", remote)
except RuntimeError:
outcome["status"] = "update_blocked"
return outcome
finally:
for path, data in saved.items():
if not path.exists() or path.read_bytes() != data:
path.parent.mkdir(parents=True, exist_ok=True)
path.write_bytes(data)
outcome["status"] = "updated"
outcome["local_commit"] = remote
return outcome
def on_use(skill_dir, now=None):
"""Record every invocation; claim today's check before the network call."""
root = Path(skill_dir).resolve()
state_path = root / "scripts" / "update-state.json"
lock_path = root / "scripts" / "update-state.lock"
now = now or datetime.now().astimezone()
stamp, day = now.isoformat(timespec="seconds"), now.date().isoformat()
try:
with _state_lock(lock_path):
state = _read_state(state_path)
state["last_used_at"] = stamp
should_check = state.get("last_check_date") != day
if should_check:
state.update(last_check_date=day, last_checked_at=stamp, status="checking")
state.pop("local_commit", None)
state.pop("remote_commit", None)
_write_state(state_path, state)
if not should_check:
return False
try:
outcome = _check_remote(root)
except (OSError, subprocess.SubprocessError, RuntimeError):
outcome = {"status": "check_failed"}
with _state_lock(lock_path):
state = _read_state(state_path)
# Preserve usage from concurrent invocations, and a newer day's claim.
if state.get("last_check_date") == day:
state.update(outcome)
_write_state(state_path, state)
if outcome["status"] == "updated":
print("[Skill] 已快进安装 Git 新版本,重新启动命令。", file=sys.stderr)
elif outcome["status"] == "update_blocked":
print("[Skill] 检测到远端版本变化,本机修改或分支状态阻止快进更新;继续执行原命令。", file=sys.stderr)
elif outcome["status"] == "check_failed":
print("[Skill] 今日更新检测失败,继续执行原命令;下次使用日自动重试。", file=sys.stderr)
return outcome["status"] == "updated"
except OSError:
print("[Skill] 无法保存本机使用记录,继续执行原命令。", file=sys.stderr)
return False
+160
View File
@@ -0,0 +1,160 @@
import contextlib
import io
import json
import pathlib
import shutil
import subprocess
import sys
import tempfile
import unittest
from datetime import datetime, timedelta, timezone
from unittest import mock
from scripts import skill_update
class DailyUpdateTests(unittest.TestCase):
def setUp(self):
self.temp = tempfile.TemporaryDirectory()
self.addCleanup(self.temp.cleanup)
self.root = pathlib.Path(self.temp.name)
self.now = datetime(2026, 9, 30, 9, tzinfo=timezone(timedelta(hours=8)))
def state(self, root=None):
return json.loads(((root or self.root) / "scripts/update-state.json").read_text(encoding="utf-8"))
def git(self, root, *args):
result = subprocess.run(
["git", "-C", str(root), *args], check=True,
stdout=subprocess.PIPE, stderr=subprocess.PIPE,
)
return result.stdout.decode().strip()
def commit(self, root, message):
self.git(root, "add", "--all")
self.git(root, "-c", "user.name=Update Test", "-c", "user.email=update@example.test", "commit", "-m", message)
def repository(self):
remote, source, installed = [self.root / name for name in ("remote.git", "source", "installed")]
self.git(self.root, "init", "--bare", str(remote))
self.git(self.root, "init", "-b", "main", str(source))
(source / "config.json").write_bytes(b'{"local":"keep"}\n')
(source / "VERSION").write_text("1.0.0\n")
(source / ".gitignore").write_text("scripts/update-state*\nscripts/config.json\n")
self.commit(source, "initial")
self.git(source, "remote", "add", "origin", str(remote))
self.git(source, "push", "origin", "main")
self.git(self.root, "clone", "-b", "main", str(remote), str(installed))
(installed / "scripts").mkdir()
(installed / "scripts/config.json").write_bytes(b'{"access_token":"local-only"}')
return source, installed
def publish(self, source, config=False):
(source / "VERSION").write_text("1.0.1\n")
if config:
(source / "config.json").write_bytes(b'{"new":"template"}\n')
self.commit(source, "update")
self.git(source, "push", "origin", "main")
def test_once_per_local_day_and_every_usage_timestamp(self):
with mock.patch.object(skill_update, "_check_remote", return_value={"status": "up_to_date"}) as check:
skill_update.on_use(self.root, self.now)
skill_update.on_use(self.root, self.now + timedelta(hours=2))
self.assertEqual(check.call_count, 1)
self.assertEqual(self.state()["last_used_at"], "2026-09-30T11:00:00+08:00")
skill_update.on_use(self.root, self.now + timedelta(days=1))
self.assertEqual(check.call_count, 2)
def test_failed_check_does_not_block_or_repeat_today_or_leak_output(self):
with mock.patch.object(skill_update, "_check_remote", side_effect=RuntimeError("secret-url")) as check:
output, error = io.StringIO(), io.StringIO()
with contextlib.redirect_stdout(output), contextlib.redirect_stderr(error):
self.assertFalse(skill_update.on_use(self.root, self.now))
skill_update.on_use(self.root, self.now)
self.assertEqual(check.call_count, 1)
self.assertEqual(output.getvalue(), "")
self.assertNotIn("secret-url", error.getvalue())
self.assertNotIn("secret-url", json.dumps(self.state()))
self.assertEqual(self.state()["status"], "check_failed")
def test_fast_forward_keeps_modified_local_login_configuration(self):
source, installed = self.repository()
(installed / "config.json").write_bytes(b'{"local":"modified"}')
original = (installed / "config.json").read_bytes()
self.publish(source)
self.assertTrue(skill_update.on_use(installed, self.now))
self.assertEqual(self.git(installed, "rev-parse", "HEAD"), self.git(source, "rev-parse", "HEAD"))
self.assertEqual((installed / "config.json").read_bytes(), original)
self.assertEqual((installed / "scripts/config.json").read_bytes(), b'{"access_token":"local-only"}')
self.assertEqual(self.state(installed)["status"], "updated")
def test_clean_config_preserved_when_upstream_template_changes(self):
source, installed = self.repository()
original = (installed / "config.json").read_bytes()
self.publish(source, config=True)
self.assertTrue(skill_update.on_use(installed, self.now))
self.assertEqual((installed / "config.json").read_bytes(), original)
self.assertEqual((installed / "VERSION").read_text(), "1.0.1\n")
def test_source_edits_block_install_but_do_not_discard_changes(self):
source, installed = self.repository()
self.publish(source)
(installed / "VERSION").write_text("local edit\n")
head = self.git(installed, "rev-parse", "HEAD")
self.assertFalse(skill_update.on_use(installed, self.now))
self.assertEqual(self.git(installed, "rev-parse", "HEAD"), head)
self.assertEqual((installed / "VERSION").read_text(), "local edit\n")
self.assertEqual(self.state(installed)["status"], "update_blocked")
def test_conflicting_remote_config_keeps_dirty_local_config(self):
source, installed = self.repository()
(installed / "config.json").write_bytes(b'{"local":"changed"}')
self.publish(source, config=True)
head = self.git(installed, "rev-parse", "HEAD")
self.assertFalse(skill_update.on_use(installed, self.now))
self.assertEqual(self.git(installed, "rev-parse", "HEAD"), head)
self.assertEqual((installed / "config.json").read_bytes(), b'{"local":"changed"}')
self.assertEqual(self.state(installed)["status"], "update_blocked")
def test_cli_restarts_into_new_version_and_keeps_stdout_json(self):
source, installed = self.repository()
(source / "scripts").mkdir()
scripts = pathlib.Path(skill_update.__file__).parent
for name in ("oracle_skill.py", "skill_update.py"):
shutil.copyfile(str(scripts / name), str(source / "scripts" / name))
self.commit(source, "add cli")
self.git(source, "push", "origin", "main")
self.git(installed, "pull", "--ff-only", "origin", "main")
self.publish(source)
result = subprocess.run(
[sys.executable, str(installed / "scripts/oracle_skill.py"), "capabilities", "--json"],
stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=True, timeout=30,
)
self.assertEqual(json.loads(result.stdout.decode("utf-8"))["version"], "1.0.1")
self.assertEqual(self.state(installed)["status"], "updated")
self.assertEqual(self.git(installed, "rev-parse", "HEAD"), self.git(source, "rev-parse", "HEAD"))
def test_diverged_branch_never_resets_local_commit(self):
source, installed = self.repository()
(installed / "VERSION").write_text("local branch\n")
self.commit(installed, "local")
self.publish(source)
head = self.git(installed, "rev-parse", "HEAD")
self.assertFalse(skill_update.on_use(installed, self.now))
self.assertEqual(self.git(installed, "rev-parse", "HEAD"), head)
self.assertEqual(self.state(installed)["status"], "update_blocked")
def test_download_without_git_does_not_fetch_parent_repository(self):
with mock.patch.object(skill_update, "_git") as git:
self.assertFalse(skill_update.on_use(self.root, self.now))
git.assert_not_called()
self.assertEqual(self.state()["status"], "not_git")
def test_concurrent_invocation_does_not_fetch_twice_or_lose_latest_usage(self):
def checking(root):
skill_update.on_use(root, self.now + timedelta(minutes=1))
return {"status": "up_to_date"}
with mock.patch.object(skill_update, "_check_remote", side_effect=checking) as check:
skill_update.on_use(self.root, self.now)
self.assertEqual(check.call_count, 1)
self.assertEqual(self.state()["last_used_at"], "2026-09-30T09:01:00+08:00")