507 Commits
Author SHA1 Message Date
pnoker 21e0bae93c refactor(driver): collapse duplicated hex and checksum helpers into codecutil
five drivers carried private bytesToHex copies and three carried
hexToBytes variants, while mbus/dlt645 frames each implemented the same
sum checksum:

- codecutil.bytesToHex now uses hexformat uppercase, matching what all
  five private copies already emitted (zero display change)
- codecutil.hexToBytes added: whitespace-tolerant, blank-to-empty,
  mixed-case input
- codecutil.sumChecksum added as the shared mbus/dlt645 frame checksum
  and both frame classes delegate to it
- dlt645's inline hex loop replaced by codecutil.hexTobytes
- fins' private bcd copy kept deliberately: codecutil's bcd variant
  strips leading zeros, a different protocol semantic
2026-09-18 19:45:24 +08:00
pnoker dc2164f46e fix(driver): repair boot, scheduling and registration regressions
a live-stack sweep of all 36 drivers surfaced five sdk-level defects
that left reads dead or the whole fleet unbootable:

- drop the empty spring.main stub in the shared driver profile; the
  boot 4.1 binder rejects null-valued keys and every driver died at
  startup with a bind exception
- rename the schedule enable flag to enable to match what every driver
  application.yml actually spells; with the old enabled key the read
  and custom quartz jobs were silently never scheduled
- treat proto3 zero-versions as unset when reconciling registration
  so a driver restart re-registers instead of failing the optimistic
  lock with 'driver version conflict'
- ignore the driver's own registration event that can be consumed
  before applymetadata finishes instead of logging an error
- initialize jdbc/serial connection maps eagerly: the registration
  event handler invalidates pools and nped on a lazy-null map
- ble: exclude the guava 18 pin dragged in by bluetooth-manager
- kafka: use the starter (boot 4 split autoconfigurations out of the
  bare jar) and read the compose-provided bootstrap variable
- lwm2m: pin californium to the 3.x api leshan m14 needs and
  configure an explicit coap endpoint
- mqtt: replace the orphaned spring-integration gateway with a paho
  publisher so the send path exists again
- boot sweep result: 36/36 drivers start, 0 errors
2026-09-18 09:48:19 +08:00
pnoker 66aab58f20 build: standardize repository tooling and quality gates 2026-09-02 20:51:10 +08:00
pnoker eae86b58fc refactor(storage)!: unify platform persistence on reactive postgresql
- migrate auth, manager, data, and agentic persistence to R2DBC
- hard cut pagination, facade, gateway, web, and CLI contracts
- remove legacy database and TSDB adapters and document validation gates

BREAKING CHANGE: platform persistence now requires PostgreSQL 18 with TimescaleDB and the new reactive API contracts.
2026-09-01 22:03:16 +08:00
pnokerandClaude fb97274691 build(deps): bump spring boot to 4.1.1 and refresh dependency stack
- Spring Boot 4.0.7 -> 4.1.1, Spring Cloud 2025.1.2 -> 2025.1.3,
  Spring AI 2.0.0 -> 2.0.1, springdoc 3.0.3 -> 3.1.0
- Migrate gRPC starters to the Boot-owned spring-boot-starter-grpc-client/server
  line (org.springframework.grpc starter stops at 1.0.3 and mixes badly with the
  spring-grpc-core 1.1.1 managed by the Boot 4.1 BOM), and move the
  spring.grpc.channels.* YAML keys to the new spring.grpc.channel.* format
- Refresh driver protocol libraries (modbus4j 3.1.0, jSerialComm 2.11.4,
  bacnet4j 6.1.1, gurux-dlms 4.0.96, j60870 1.8.0, snmp4j 3.13.1) and drop the
  flow-control overrides removed from the modbus4j SerialPortWrapper interface
- Refresh jackson 2.x/3.x, protobuf, okhttp, micrometer, oshi, sqlite-jdbc,
  swagger-core, instancio, mockito, logstash-logback-encoder 9.0 and the
  graalvm native plugin; keep commons-io (broken timestamp-version candidate)
  and j-interop 2.0.4 (legacy major) pinned

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-27 07:47:23 +08:00
pnoker a7ca489b9b style(repo): reformat java, xml, yaml, markdown and frontend sources 2026-08-25 21:27:49 +08:00
pnoker 11640cf6ac build: deploy dc3-db artifacts and document store selection env
- include dc3-db in the make deploy artifact set alongside common/mq/tsdb
- sample DC3_DB_TYPE/DC3_TSDB_TYPE and external-store ports in .env.example
- expose mysql/mariadb/tdengine/influxdb/iotdb/jaeger in the compose stacks
- update AGENTS.md module map and module-ownership guidance
- add the missing AGPL header to the oracle driver service
2026-08-25 19:27:57 +08:00
pnoker 769d1cb977 style: apply repository-wide formatting
- expand single-line Javadocs to multi-line blocks, rewrap Markdown prose, and reflow POM, YAML, and TypeScript formatting
- formatting only: no Java code lines changed, no build-file semantics changed
- verified: backend compile and check_documentation.py pass, pnpm check and pnpm lint:check pass, and the dc3 YAML files parse
2026-08-25 13:35:00 +08:00
pnoker 1fa2d6f0cf docs(comments): fill the real comment gaps across the repository
A comment-health audit driven by an AST scanner (regex scanners kept
false-positiving on annotation-separated javadoc; the AST pass with
constructor and @Override-implementation exemptions is what produced a
trustworthy worklist) ended with a much smaller true gap than the raw
numbers suggested: class-level javadoc is already at 100% repository-wide.

What actually gets filled here:

- AnalyticsModel: all 30 nested records of the S19 analytics facet get
  maintainer-voice javadoc (the @Schema descriptions serve the API side;
  these serve code readers).
- The whole entity/ext family (29 files, 47 nested classes): every
  Content/Ui/Validation/Security/AppliesTo/Template/Dedup/... schema
  class states what it actually holds — read from the fields, not
  templated.
- Adapter families: the @Bean methods of all six MQ adapters, all four
  TSDB adapters and the three DB dialect adapters get one-line
  maintainer docs (what binds to what, what is overridable).
- Nested support types across data/agentic/constant/public/facade:
  NotifyBindKey, CacheTuning, Credential, dashboard VOs' Item/BucketVO,
  ExpireListener (plus its null-doc placeholder javadoc replaced with a
  real contract), agentic chat/stream VOs' choice/delta/usage shapes,
  tool-context records, RequestHeader envelopes, FleetSummary,
  visualization Type/Scale codes.
- PointValueSampleConverter's boundary methods now state the port-side
  contract (quality default injection point, batch order preservation).

Comment drift fixed: the class javadoc of the can/mqtt/opc-da driver
skeletons claimed "see TODO markers in method bodies" — those markers
do not exist; the claims are corrected to plain work-in-progress notes.
The genuine TODOs (zigbee API verification, lwm2m lookup, ethernet-ip
CIP framing, CAN JNI) stay: they mark real unimplemented behavior.

Vendored-code boundary: the opc-da module carries a ported
org.openscada library (46 files, package org.openscada.*). Its trivial
accessors stay bare on purpose — filler comments on get/set pairs are
exactly the kind of noise this pass exists to remove — but the
non-obvious JIStruct wire-decoder methods (getStruct/fromStruct
FILETIME + three DO wrappers) now document the COM bridge shape.

Gates: full-repo compile green; data/model/constant/agentic/public
test suites green.
2026-08-24 21:23:39 +08:00
pnoker 4371650a6e refactor(sql): align jdbc driver base with documented semantics
Apply queryTimeout as a statement timeout instead of a connection timeout, validate point queries against the point read/write flag, close all connection pools on driver metadata events, and remove the unused DriverSenderService dependency. Rename the module unit test so surefire executes it (26 tests) instead of excluding it via the Abstract* pattern.
2026-08-24 21:00:57 +08:00
pnoker 51f0efd2d9 style(javadoc): document non-override public methods 2026-08-19 00:05:58 +08:00
pnoker dddf2e8690 test: enforce truthful coverage and behavioral quality 2026-08-18 23:24:58 +08:00
pnoker caf34ba85e fix(driver): isolate hardware resources and validate protocol I/O 2026-08-18 23:24:08 +08:00
pnoker aec6ebfc76 refactor(logging): standardize application logging 2026-08-18 08:14:52 +08:00
pnoker 956de3dd38 feat(driver)!: enforce lease-fenced durable telemetry
Use PostgreSQL leases and fencing for distributed ownership, require a durable SQLite outbox before RabbitMQ publication, and make Data Center ingestion transactional and idempotent.

BREAKING CHANGE: drivers require mandatory durable outbox configuration and use lease-aware ownership and telemetry contracts.
2026-08-18 00:50:57 +08:00
pnoker def5c34378 build(driver): register eight new drivers in pom dockerfile and compose 2026-08-17 20:11:19 +08:00
pnoker 25541646c8 feat(driver): add dnp3 master driver over native stack 2026-08-17 20:11:18 +08:00
pnoker 5aa6457aad feat(driver): add iec61850 driver for substation automation 2026-08-17 20:11:18 +08:00
pnoker dea95bf341 feat(driver): add knx driver for knx building automation 2026-08-17 20:11:17 +08:00
pnoker a137eeb0d3 feat(driver): add lorawan driver for chirpstack mqtt uplink 2026-08-17 20:11:16 +08:00
pnoker 314dae66a5 feat(driver): add kafka driver for kafka streaming data source 2026-08-17 20:11:16 +08:00
pnoker 9d00105c48 feat(driver): add redis driver for redis key-value data source 2026-08-17 20:11:15 +08:00
pnoker 9a2a094881 feat(driver): add mbus driver for EN13757 meter bus 2026-08-17 20:11:15 +08:00
pnoker 78c4e6d531 feat(driver): add dlt645 driver for DL/T645-2007 electricity meters 2026-08-17 20:11:14 +08:00
hongyuan.zhang 6b37cb5ccd docs(readme): refresh module documentation 2026-08-11 22:58:30 +08:00
pnoker 50c6de9438 docs: rewrap project guides and README files
Prose rewrapping and table reflow from a project-wide markdown pass;
no content changes. Also reverts two formatter mistakes: restores
MD5(password) in dc3-web/AGENTS.md and drops spurious spaces after
CJK punctuation in README.zh.md / README.ja.md.

Covers root guides (AGENTS/CONTRIBUTING/SECURITY/README.*), module
READMEs across dc3-api/center/common/driver/e2e/gateway, the nginx
guide, dc3/doc/{README,TITLE}.md, and dc3-web docs.
2026-08-09 18:44:55 +08:00
Henry ZhangandClaude Fable 5 73ff48bcb9 style: reformat codebase (backend java/xml, docs, web) (#175)
* style(backend): reformat and rearrange java/xml sources

Apply code cleanup across dc3-common and dc3-driver: whitespace and
indent normalization, import sorting, member rearrangement, and javadoc
normalization. No semantic changes; compilation verified.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* style(docs): reformat vitepress vue diagram components

Expand compact svg templates and normalize formatting across diagram
components, the seo module, and the theme stylesheet. No behavior
changes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* style(docs): reformat markdown pages

Normalize markdown formatting. Restore the <script setup> component
imports that an earlier unused-import pass had wrongly stripped; every
<Diagram> tag now resolves to a backing import again.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* style: reformat point view and trim pom whitespace

Normalize whitespace in the point view and drop trailing whitespace in
pom.xml. No behavior changes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-06 12:13:36 +08:00
pnoker 056bd9ae21 Merge remote-tracking branch 'origin/develop' into chore/promote-develop-main-3 2026-08-03 21:57:03 +08:00
Henry Zhang 00c67406b0 refactor(quality): fix unused-param/confusing-sig/local-temp/js-comparison (12) (#163)
- unused-parameter (7 private/non-override): drop unused params in
  Snmp getConnector/buildTarget, Sl651 handleSl651Message,
  PointCommandValidator.validateWriteValue, TypedValueConverter
  finiteFloat/finiteDouble (+ cascade callers). 3 interface default
  methods kept (contract).
- confusing-method-signature (1): AuthenticGatewayFilterFactory
  <Object> → dedicated Config type; apply(Object)→apply(Config)
- local-temp-file-info-disclosure (1): FileUtil creates temp dirs
  with owner-only rwx------ via PosixFilePermissions (Windows fallback)
- js/comparison-between-incompatible-types (1): isNull drop redundant
  val === undefined (== null loose already covers it)

Behavior-preserving. Compile + test-compile clean.
2026-08-03 21:55:13 +08:00
Henry Zhang 4e2c5bc770 refactor(quality): fix 27 medium-risk CodeQL alerts (null/exception/boundary/exposure) (#162)
- non-null-boxed (7): GroupServiceImpl extract Byte→byte; DashboardServiceImpl
  Long→long for primitive id fields
- uncaught-number-format (7): try-catch parse failures in FINS/EthernetIP/BACnet/
  Dashboard (fall back to 0 / skip, matching existing pattern); remove dead
  unreachable outer catch in BACnet
- dereferenced-null (4): DeviceMetadata expand MapUtils.isEmpty to explicit
  null check for short-circuit clarity
- ignored-error-status (4): ConcurrentLinkedQueue.offer→add (unbounded, offer
  always true; add's boolean is "modified", not error)
- index-out-of-bounds (2): orderedMap(Object...) guard odd-length → IAE
- internal-rep-exposure (3): CoverageGapVO/Pages/DriverMetadata return
  unmodifiable views + addX/removeX mutators; migrate all callers + tests

Behavior-preserving. Compile clean (except bacnet4j pre-existing mirror miss).
2026-08-03 21:49:22 +08:00
Henry Zhang 852f0682d3 refactor(quality): fix 21 low-risk CodeQL quality alerts (#160)
- local-variable-is-never-read (3): dead host/port/database locals in
  AbstractJdbcDriverCustomService (subclasses read config themselves)
- unused-container (2): unused `valid` set in AlarmRuleTriggerServiceImpl;
  unused driverSenderService field/ctor param in CanDriver (+ test)
- unknown-javadoc-parameter (12): dangling ctor Javadoc on
  @RequiredArgsConstructor classes / stale @param
- call-to-object-tostring (3): @ToString on MessageHeader (root cause);
  field-log MetadataEventDTO instead of hash
- constant-comparison (1): DriverInitRunner == → >= (defensive)

No behavior change. Compile clean (agentic/can/data/auth/mqtt/driver/sql).
2026-08-03 21:49:03 +08:00
pnoker 4cd16d23d1 Merge remote-tracking branch 'origin/develop' into chore/promote-develop-main-2 2026-08-03 20:24:41 +08:00
Henry Zhang fa1cd49bac fix(security): harden log-injection + CAN command injection (#158)
* fix(security): sanitize agentic assistant HTML with DOMPurify (XSS)

RenderedAssistantMessage rendered model output via v-html after a
hand-rolled regex sanitizer that CodeQL flagged 5 ways: bad-tag-filter,
incomplete-multi-character-sanitization (on* attributes, javascript:),
incomplete-url-scheme-check (data:/vbscript:). Replace with DOMPurify,
which covers all these plus mutation XSS. Fixes CodeQL #2146-2150.

* fix(security): harden log-injection + CAN command injection

log-injection (7): pass user-derived log args (conversationId, model,
candidate/fallback) through LogSanitizer.sanitize in AgenticChatServiceImpl,
ChatClientFactory, AgenticChatRequestPreparer — strips CR/LF that could
forge log lines.

relative-path-command / command injection (2, CanDriver): executeCommand
runs `sh -c` with a command built from driver config (interfaceName) and
request data (canId/frame), so add a CAN_COMMAND_PATTERN whitelist
(^[A-Za-z0-9 _#,.=%/-]+$) rejecting shell metacharacters, and validate
interfaceName against ^[A-Za-z0-9_]+$ at all 3 entry points. Fixes
CodeQL #2173,#2172,#2120,#1514,#1513,#1022,#1017,#1016,#1014.

Verified: mvn compile agentic+can clean.
2026-08-03 19:46:42 +08:00
Henry Zhang 4dd477163e build(deps): bump californium 4.0.0-M3 → 4.0.0-M6 + adapt CoapExchange import (#156)
M6 relocated CoapExchange from core.server.resources to core (verified
via compile). dc3-driver-coap uses only the core CoAP API
(CoapClient/Server/Endpoint/Resource/Configuration) and avoids the
DTLS/observe/MessageObserver areas where M3→M6 broke, so this single
import is the whole adaptation. Supersedes dependabot #81.

Gains: M4-M6 bugfixes (deferred CID records, CON notify reliability,
anonymous cipher-suite), Java 21 virtual-thread support, RFC 7252
compliance. 4.0.0 GA has no timeline; no reason to stall on M3.
2026-08-03 14:23:28 +08:00
pnoker 7bf615b3b1 feat(driver): configure driver buffer and mount container volume
28 driver application.yml add dc3.driver.buffer; Dockerfile each stage adds dc3/data dir and VOLUME; docker-compose main/dev add driver_data named volume
2026-07-26 00:13:31 +08:00
dependabot[bot] ca359f6fc1 build(deps): bump com.infiniteautomation:bacnet4j from 6.0.1 to 6.1.0 (#110)
Bumps [com.infiniteautomation:bacnet4j](https://github.com/infiniteautomation/BACnet4J) from 6.0.1 to 6.1.0.
- [Release notes](https://github.com/infiniteautomation/BACnet4J/releases)
- [Commits](https://github.com/infiniteautomation/BACnet4J/compare/v6.0.1...v6.1.0)

---
updated-dependencies:
- dependency-name: com.infiniteautomation:bacnet4j
  dependency-version: 6.1.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-19 20:30:24 +08:00
dependabot[bot] af02d90024 build(deps): bump org.snmp4j:snmp4j from 3.7.4 to 3.12.2 (#109)
Bumps org.snmp4j:snmp4j from 3.7.4 to 3.12.2.

---
updated-dependencies:
- dependency-name: org.snmp4j:snmp4j
  dependency-version: 3.12.2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-19 20:30:18 +08:00
pnokerandClaude 741ad7cc44 feat(security): redact identifiers and model names in logs
Add LogSanitizer and apply it to agentic conversationId/model and the listening-virtual netty deviceName so they are not emitted verbatim in log output.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-16 20:03:59 +08:00
pnokerandClaude 3f083e7ee8 fix: guard numeric parsing against NumberFormatException
Wrap unchecked Integer/Long/Byte.parse* calls in topic/dashboard/oauth/facade and the BACnet-IP, Ethernet-IP, FINS, SNMP drivers plus the agentic action payload, so malformed values fall back to a safe default instead of aborting.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-16 20:03:42 +08:00
pnokerandClaude 0ff5f7a14f style(opc-da): add missing @Override to openscada adapter methods
Annotate overriding methods in the vendored org.openscada OPC-DA adapter with @Override.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-16 20:03:41 +08:00
pnokerandClaude fe28d41803 refactor(config): drop redundant YAML blocks covered by shared profile bases
Remove configuration duplicated verbatim by the auto-activated shared profile bases: the dc3-center-manager gRPC channel in several drivers, the management.* block in center/gateway services, the gateway swagger-ui.path, duplicate logging sub-packages, and dead dc3.driver.mqtt config in dc3-center-data (no dc3-common-mqtt dependency). spring.env/spring.group are kept (consumed by RabbitmqEnvironmentConfig/WebFluxSecurityConfig/HmacAuthConfig). Effective configuration unchanged; validated via YAML parse + Maven process-resources.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-16 19:45:16 +08:00
pnokerandClaude Fable 5 3a031073ca fix(driver-coap): bind CoAP server to the configured serverHost
The server was constructed with only CoapConfig.COAP_PORT set, so
Californium created a default endpoint bound to 0.0.0.0 (all
interfaces) — coapProperties.serverHost was never applied, yet the
startup log printed it as the listen host. Setting serverHost to a
specific interface silently failed to restrict binding. Add an explicit
CoapEndpoint bound to InetSocketAddress(serverHost, serverPort) so the
property takes effect and the log is accurate.

Verified: dc3-driver-coap compiles.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 17:47:32 +08:00
pnokerandClaude Fable 5 b01b2802db fix(driver-fins): correct 32-bit read length and FLOAT write encoding
Two runtime bugs in the FINS driver:

1. read() hardcoded a 1-word (2-byte) memory-read request regardless of
   dataType, so INT32/UINT32/FLOAT decodes (which need 4 bytes) always
   threw BufferUnderflowException — 32-bit points could never be read.
   Derive the word count from dataType (2 words for 32-bit types).

2. encodeWriteData grouped FLOAT with INT32/UINT32, doing
   Integer.parseInt + putInt. Writing "3.14" threw NumberFormatException
   and "3" was written as int bits (read back as ~4.2E-45 via getFloat).
   Give FLOAT its own branch using Float.parseFloat + putFloat.

Relaxed encodeWriteData/decodeValue/wordCount to package-private and
added round-trip tests (FLOAT, INT32) plus wordCount coverage.
Verified: dc3-driver-fins 7 tests pass.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 17:45:18 +08:00
pnoker d52b106660 style: align Javadoc @param tags and docs table formatting
- Align @param tag indentation in AgenticChatResponseCodec, GrpcFacadeSupport, PlcS7DriverCustomServiceImpl
- Align markdown table columns in docs/en and docs/zh architecture, technology-stack, operation, quickstart
- Reformat vitepress config group items for readability
2026-07-13 14:22:38 +08:00
pnoker 398026fc95 docs(drivers): document plcs7 and melsec protocol helpers
- PlcS7DriverCustomServiceImpl: document getS7PLC/buildVariable/
  readByType/writeByType/invalidateConnection/closeConnection
- MelsecDriverCustomServiceImpl: document the same six helpers
  (McPLC variant); both drivers shared the undocumented helper pattern
  while modbus-tcp/mqtt/opc-ua already documented theirs
2026-07-10 12:14:22 +08:00
pnoker 0fd8d5b6b3 docs(drivers): align since tags with version across protocol drivers
Fix @since 2026.6.2 -> @since 2026.5.22 on 40 files across 17 protocol
drivers (serial, dlms, fins, mysql, ethernet-ip, zigbee, bacnet-ip,
snmp, ble, can, oracle, postgresql, iec104, lwm2m, tcp-udp, http,
sqlserver). The @since value was later than @version 2026.5.22, which
is impossible — a class cannot exist in 5.22 yet be introduced in 6.2.
2026-07-09 19:18:28 +08:00
pnokerandClaude f263164039 style: rename unused catch parameters to 'ignored'
Replace unused catch exception parameter names ('e', 'ex', etc.) with
the conventional 'ignored' identifier to suppress IDE warnings across
the codebase. This is a purely mechanical rename — no behavioral change.

Affected modules: enums (40+), drivers (12+), common-data, common-driver,
common-manager, common-auth, common-public, common-agentic, common-web,
common-resource-registrar, and all driver implementations.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-09 12:48:14 +08:00
Henry Zhang 91d8717231 fix(driver): resolve 4 real defects in OPC-UA and MQTT drivers (#112)
* fix(driver-mqtt): report real broker connection state in driver health

* fix(driver-opc-ua): expose certificate-degraded marker in health description

* fix(driver-opc-ua): use x509 identity provider when certificate is configured

* fix(driver-opc-ua): add connection backoff to prevent connect storms on unreachable devices
2026-07-01 14:27:35 +08:00
pnoker d18bf4d03d style: fix table alignment and whitespace in README files 2026-06-26 13:17:38 +08:00
pnoker a6c63a9c3d style: fix formatting in Java source files and tests 2026-06-26 13:17:33 +08:00