feat: expand vehicle data platform capabilities

This commit is contained in:
lingniu
2026-07-27 16:46:15 +08:00
parent e3a1f80f86
commit 3c4bece72c
650 changed files with 62155 additions and 2552 deletions

View File

@@ -0,0 +1,121 @@
#!/usr/bin/env bash
set -euo pipefail
RELEASE_ID=${1:?release id is required}
WEB_ARCHIVE=${2:?open portal web archive is required}
API_BINARY=${3:?open platform API binary is required}
ROOT=${OPEN_PLATFORM_ROOT:-/opt/lingniu-vehicle-open-platform}
BASE_URL=${OPEN_PLATFORM_BASE_URL:-http://127.0.0.1:20310}
SERVICE=${OPEN_PLATFORM_SERVICE:-lingniu-vehicle-open-platform}
SYSTEMCTL_BIN=${SYSTEMCTL_BIN:-systemctl}
CURL_BIN=${CURL_BIN:-curl}
RELEASE_HISTORY_LIMIT=${RELEASE_HISTORY_LIMIT:-12}
case "$RELEASE_ID" in
''|*[!A-Za-z0-9._-]*) printf 'invalid release id: %s\n' "$RELEASE_ID" >&2; exit 1 ;;
esac
test -f "$WEB_ARCHIVE" || { printf 'open portal archive is missing\n' >&2; exit 1; }
test -f "$API_BINARY" || { printf 'open platform API binary is missing\n' >&2; exit 1; }
old=
if test -L "$ROOT/current"; then
old=$(readlink -f "$ROOT/current")
fi
next="$ROOT/releases/$RELEASE_ID"
env_file="$ROOT/env/open-platform.env"
switched=false
set_release_env() {
python3 - "$env_file" "$1" <<'PY'
import pathlib
import sys
path = pathlib.Path(sys.argv[1])
release = sys.argv[2]
lines = path.read_text(encoding="utf-8").splitlines() if path.exists() else []
updated = []
found = False
for line in lines:
if line.startswith("OPEN_PLATFORM_RELEASE="):
updated.append("OPEN_PLATFORM_RELEASE=" + release)
found = True
else:
updated.append(line)
if not found:
updated.append("OPEN_PLATFORM_RELEASE=" + release)
path.write_text("\n".join(updated) + "\n", encoding="utf-8")
PY
chmod 0600 "$env_file"
}
rollback() {
status=$?
trap - ERR
set +e
if test "$switched" = true && test -n "$old"; then
ln -s "$old" "$ROOT/current.rollback"
python3 -c 'import os,sys; os.replace(sys.argv[1],sys.argv[2])' "$ROOT/current.rollback" "$ROOT/current"
set_release_env "$(basename "$old")"
"$SYSTEMCTL_BIN" restart "$SERVICE"
elif test "$switched" = true; then
"$SYSTEMCTL_BIN" stop "$SERVICE"
rm -f "$ROOT/current"
set_release_env ""
fi
printf 'open platform release failed; current restored to %s\n' "${old:-none}" >&2
exit "$status"
}
trap rollback ERR
test ! -e "$next" || { printf 'release already exists: %s\n' "$next" >&2; exit 1; }
mkdir -p "$next/web" "$ROOT/env"
cp "$API_BINARY" "$next/open-platform-api"
chmod 0755 "$next/open-platform-api"
while IFS= read -r member; do
case "$member" in
/*|../*|*/../*|*/..) printf 'web archive contains unsafe path: %s\n' "$member" >&2; exit 1 ;;
esac
done < <(tar -tzf "$WEB_ARCHIVE")
tar --no-same-owner -xzf "$WEB_ARCHIVE" -C "$next/web"
test -f "$next/web/index.html" || { printf 'open portal index is missing\n' >&2; exit 1; }
ln -s "$next" "$ROOT/current.next"
python3 -c 'import os,sys; os.replace(sys.argv[1],sys.argv[2])' "$ROOT/current.next" "$ROOT/current"
switched=true
set_release_env "$RELEASE_ID"
"$SYSTEMCTL_BIN" restart "$SERVICE"
ready=false
for _ in $(seq 1 20); do
if "$SYSTEMCTL_BIN" is-active --quiet "$SERVICE" && "$CURL_BIN" -fsS "$BASE_URL/healthz" >/dev/null; then
ready=true
break
fi
sleep 1
done
test "$ready" = true || { printf 'open platform service did not become ready\n' >&2; exit 1; }
"$CURL_BIN" -fsS "$BASE_URL/" | grep -q '<div id="root"></div>'
"$CURL_BIN" -fsS "$BASE_URL/open-api/openapi.yaml" | grep -q '^openapi: 3.0.3'
"$CURL_BIN" -fsS "$BASE_URL/portal-api/catalog" | grep -q 'daily_hydrogen'
python3 - "$ROOT" "$RELEASE_HISTORY_LIMIT" "$next" "$old" <<'PY'
import pathlib
import shutil
import sys
root = pathlib.Path(sys.argv[1])
limit = int(sys.argv[2])
protected = {pathlib.Path(value).resolve() for value in sys.argv[3:] if value}
releases = sorted(
(p for p in (root / "releases").iterdir() if p.is_dir()),
key=lambda p: p.stat().st_mtime,
reverse=True,
)
for path in releases[limit:]:
if path.resolve() not in protected:
shutil.rmtree(path)
PY
switched=false
trap - ERR
printf 'open_platform_release_install=ok release=%s previous=%s\n' "$RELEASE_ID" "${old##*/}"

View File

@@ -0,0 +1,48 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
FIXTURE=$(mktemp -d)
trap 'rm -rf "$FIXTURE"' EXIT
ROOT="$FIXTURE/root"
mkdir -p "$FIXTURE/web" "$FIXTURE/bin" "$ROOT/env"
printf '<!doctype html><div id="root"></div>\n' > "$FIXTURE/web/index.html"
printf 'binary\n' > "$FIXTURE/open-platform-api"
tar -C "$FIXTURE/web" -czf "$FIXTURE/web.tar.gz" .
cat > "$ROOT/env/open-platform.env" <<'EOF'
OPEN_PLATFORM_HTTP_ADDR=:20310
EOF
cat > "$FIXTURE/bin/systemctl" <<'EOF'
#!/usr/bin/env bash
set -e
case "$1" in
restart|stop) exit 0 ;;
is-active) exit 0 ;;
esac
EOF
cat > "$FIXTURE/bin/curl" <<'EOF'
#!/usr/bin/env bash
set -e
url=${@: -1}
case "$url" in
*/healthz) printf '{"status":"ok"}\n' ;;
*/open-api/openapi.yaml) printf 'openapi: 3.0.3\n' ;;
*/portal-api/catalog) printf '{"data":[{"code":"daily_hydrogen"}]}\n' ;;
*) printf '<div id="root"></div>\n' ;;
esac
EOF
chmod +x "$FIXTURE/bin/systemctl" "$FIXTURE/bin/curl"
OPEN_PLATFORM_ROOT="$ROOT" \
SYSTEMCTL_BIN="$FIXTURE/bin/systemctl" \
CURL_BIN="$FIXTURE/bin/curl" \
"$SCRIPT_DIR/install-open-platform-release.sh" release-1 "$FIXTURE/web.tar.gz" "$FIXTURE/open-platform-api"
test "$(readlink "$ROOT/current")" = "$ROOT/releases/release-1"
test -x "$ROOT/current/open-platform-api"
test -f "$ROOT/current/web/index.html"
grep -q '^OPEN_PLATFORM_RELEASE=release-1$' "$ROOT/env/open-platform.env"
printf 'install_open_platform_release_test=ok\n'

View File

@@ -7,9 +7,16 @@ ARCHIVE=${2:?web archive is required}
API_BINARY=${3:-}
ONEOS_SCOPE_BINARY=${4:-}
RECONCILIATION_BINARY=${5:-}
OPEN_STAT_BINARY=${6:-}
ALERT_EVALUATOR_BINARY=${7:-}
ALERT_STREAM_EVALUATOR_BINARY=${8:-}
ALERT_NOTIFICATION_DISPATCHER_BINARY=${9:-}
ROOT=${PLATFORM_ROOT:-/opt/lingniu-vehicle-platform}
BASE_URL=${PLATFORM_BASE_URL:-http://127.0.0.1:20300}
SERVICE=${PLATFORM_SERVICE:-lingniu-vehicle-platform}
ALERT_EVALUATOR_SERVICE=${ALERT_EVALUATOR_SERVICE:-lingniu-vehicle-alert-evaluator}
ALERT_STREAM_EVALUATOR_SERVICE=${ALERT_STREAM_EVALUATOR_SERVICE:-lingniu-vehicle-alert-stream-evaluator}
ALERT_NOTIFICATION_DISPATCHER_SERVICE=${ALERT_NOTIFICATION_DISPATCHER_SERVICE:-lingniu-vehicle-alert-notification-dispatcher}
COMPATIBILITY_GENERATIONS=${COMPATIBILITY_GENERATIONS:-3}
RELEASE_HISTORY_LIMIT=${RELEASE_HISTORY_LIMIT:-20}
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
@@ -29,6 +36,18 @@ fi
if test -n "$RECONCILIATION_BINARY"; then
test -f "$RECONCILIATION_BINARY" || { printf 'reconciliation evaluator binary is missing: %s\n' "$RECONCILIATION_BINARY" >&2; exit 1; }
fi
if test -n "$OPEN_STAT_BINARY"; then
test -f "$OPEN_STAT_BINARY" || { printf 'open platform statistics binary is missing: %s\n' "$OPEN_STAT_BINARY" >&2; exit 1; }
fi
if test -n "$ALERT_EVALUATOR_BINARY"; then
test -f "$ALERT_EVALUATOR_BINARY" || { printf 'alert evaluator binary is missing: %s\n' "$ALERT_EVALUATOR_BINARY" >&2; exit 1; }
fi
if test -n "$ALERT_STREAM_EVALUATOR_BINARY"; then
test -f "$ALERT_STREAM_EVALUATOR_BINARY" || { printf 'alert stream evaluator binary is missing: %s\n' "$ALERT_STREAM_EVALUATOR_BINARY" >&2; exit 1; }
fi
if test -n "$ALERT_NOTIFICATION_DISPATCHER_BINARY"; then
test -f "$ALERT_NOTIFICATION_DISPATCHER_BINARY" || { printf 'alert notification dispatcher binary is missing: %s\n' "$ALERT_NOTIFICATION_DISPATCHER_BINARY" >&2; exit 1; }
fi
test -x "$SCRIPT_DIR/prepare-web-release-tree.sh" || { printf 'release tree helper is missing\n' >&2; exit 1; }
test -f "$SCRIPT_DIR/prune-release-history.py" || { printf 'release pruning helper is missing\n' >&2; exit 1; }
test -f "$SCRIPT_DIR/verify-customer-demo.py" || { printf 'customer demo gate is missing\n' >&2; exit 1; }
@@ -73,6 +92,9 @@ rollback() {
python3 -c 'import os,sys; os.replace(sys.argv[1], sys.argv[2])' "$ROOT/current.rollback" "$ROOT/current"
set_release_env "$old_release"
"$SYSTEMCTL_BIN" restart "$SERVICE"
test -z "$ALERT_EVALUATOR_BINARY" || "$SYSTEMCTL_BIN" restart "$ALERT_EVALUATOR_SERVICE"
test -z "$ALERT_STREAM_EVALUATOR_BINARY" || "$SYSTEMCTL_BIN" restart "$ALERT_STREAM_EVALUATOR_SERVICE"
test -z "$ALERT_NOTIFICATION_DISPATCHER_BINARY" || "$SYSTEMCTL_BIN" restart "$ALERT_NOTIFICATION_DISPATCHER_SERVICE"
fi
printf 'web release install failed; current restored to %s\n' "$old" >&2
exit "$status"
@@ -81,7 +103,7 @@ trap rollback ERR
test ! -e "$next" || { printf 'release already exists: %s\n' "$next" >&2; exit 1; }
mkdir -p "$next/web"
for runtime_file in platform-api alert-evaluator alert-stream-evaluator platform-migrate oneos-scope-sync reconciliation-evaluator alert-benchmark; do
for runtime_file in platform-api alert-evaluator alert-stream-evaluator alert-notification-dispatcher platform-migrate oneos-scope-sync reconciliation-evaluator alert-benchmark open-platform-stat; do
if test -f "$old/$runtime_file"; then
cp "$old/$runtime_file" "$next/$runtime_file"
fi
@@ -96,6 +118,18 @@ fi
if test -n "$RECONCILIATION_BINARY"; then
cp "$RECONCILIATION_BINARY" "$next/reconciliation-evaluator"
fi
if test -n "$OPEN_STAT_BINARY"; then
cp "$OPEN_STAT_BINARY" "$next/open-platform-stat"
fi
if test -n "$ALERT_EVALUATOR_BINARY"; then
cp "$ALERT_EVALUATOR_BINARY" "$next/alert-evaluator"
fi
if test -n "$ALERT_STREAM_EVALUATOR_BINARY"; then
cp "$ALERT_STREAM_EVALUATOR_BINARY" "$next/alert-stream-evaluator"
fi
if test -n "$ALERT_NOTIFICATION_DISPATCHER_BINARY"; then
cp "$ALERT_NOTIFICATION_DISPATCHER_BINARY" "$next/alert-notification-dispatcher"
fi
if test -f "$old/lingniu-vehicle-platform.service"; then
cp "$old/lingniu-vehicle-platform.service" "$next/lingniu-vehicle-platform.service"
fi
@@ -114,7 +148,7 @@ cp "$SCRIPT_DIR/verify-customer-demo.py" "$next/deploy/verify-customer-demo.py"
cp "$SCRIPT_DIR/import-source-providers.py" "$next/deploy/import-source-providers.py"
cp "$0" "$next/deploy/install-web-release.sh"
chmod +x "$next/platform-api" "$next/deploy/"*.sh "$next/deploy/prune-release-history.py" "$next/deploy/verify-customer-demo.py" "$next/deploy/import-source-providers.py"
for runtime_file in alert-evaluator alert-stream-evaluator platform-migrate oneos-scope-sync reconciliation-evaluator alert-benchmark; do
for runtime_file in alert-evaluator alert-stream-evaluator alert-notification-dispatcher platform-migrate oneos-scope-sync reconciliation-evaluator alert-benchmark open-platform-stat; do
test ! -f "$next/$runtime_file" || chmod +x "$next/$runtime_file"
done
@@ -132,6 +166,9 @@ python3 -c 'import os,sys; os.replace(sys.argv[1], sys.argv[2])' "$ROOT/current.
switched=true
set_release_env "$RELEASE_ID"
"$SYSTEMCTL_BIN" restart "$SERVICE"
test -z "$ALERT_EVALUATOR_BINARY" || "$SYSTEMCTL_BIN" restart "$ALERT_EVALUATOR_SERVICE"
test -z "$ALERT_STREAM_EVALUATOR_BINARY" || "$SYSTEMCTL_BIN" restart "$ALERT_STREAM_EVALUATOR_SERVICE"
test -z "$ALERT_NOTIFICATION_DISPATCHER_BINARY" || "$SYSTEMCTL_BIN" restart "$ALERT_NOTIFICATION_DISPATCHER_SERVICE"
ready=false
for _ in $(seq 1 15); do

View File

@@ -29,25 +29,36 @@ tar -C "$new_web" -czf "$fixture/new.tar.gz" .
printf 'new platform API\n' > "$fixture/new-platform-api"
printf 'new OneOS scope sync\n' > "$fixture/new-oneos-scope-sync"
printf 'new reconciliation evaluator\n' > "$fixture/new-reconciliation-evaluator"
printf 'new open platform statistics\n' > "$fixture/new-open-platform-stat"
printf 'new alert evaluator\n' > "$fixture/new-alert-evaluator"
printf 'new alert stream evaluator\n' > "$fixture/new-alert-stream-evaluator"
printf 'new alert notification dispatcher\n' > "$fixture/new-alert-notification-dispatcher"
printf '#!/usr/bin/env bash\nexit 0\n' > "$fixture/bin/systemctl"
printf '#!/usr/bin/env bash\nexit 0\n' > "$fixture/bin/curl"
printf '#!/usr/bin/env bash\nprintf "mock_verify=ok\\n"\n' > "$fixture/bin/verify"
chmod +x "$fixture/bin/"*
PLATFORM_ROOT="$root" SYSTEMCTL_BIN="$fixture/bin/systemctl" CURL_BIN="$fixture/bin/curl" VERIFY_WEB_RELEASE_BIN="$fixture/bin/verify" RELEASE_HISTORY_LIMIT=3 "$SCRIPT_DIR/install-web-release.sh" new-release "$fixture/new.tar.gz" "$fixture/new-platform-api" "$fixture/new-oneos-scope-sync" "$fixture/new-reconciliation-evaluator" > "$fixture/install.out"
PLATFORM_ROOT="$root" SYSTEMCTL_BIN="$fixture/bin/systemctl" CURL_BIN="$fixture/bin/curl" VERIFY_WEB_RELEASE_BIN="$fixture/bin/verify" RELEASE_HISTORY_LIMIT=3 "$SCRIPT_DIR/install-web-release.sh" new-release "$fixture/new.tar.gz" "$fixture/new-platform-api" "$fixture/new-oneos-scope-sync" "$fixture/new-reconciliation-evaluator" "$fixture/new-open-platform-stat" "$fixture/new-alert-evaluator" "$fixture/new-alert-stream-evaluator" "$fixture/new-alert-notification-dispatcher" > "$fixture/install.out"
test "$(readlink -f "$root/current")" = "$(cd "$root/releases/new-release" && pwd -P)"
grep -q '^PLATFORM_RELEASE=new-release$' "$root/env/platform.env"
test "$(cat "$root/current/platform-api")" = 'new platform API'
test "$(cat "$root/current/oneos-scope-sync")" = 'new OneOS scope sync'
test "$(cat "$root/current/reconciliation-evaluator")" = 'new reconciliation evaluator'
test "$(cat "$root/current/alert-evaluator")" = 'old evaluator'
test "$(cat "$root/current/open-platform-stat")" = 'new open platform statistics'
test -x "$root/current/open-platform-stat"
test "$(cat "$root/current/alert-evaluator")" = 'new alert evaluator'
test "$(cat "$root/current/alert-stream-evaluator")" = 'new alert stream evaluator'
test "$(cat "$root/current/alert-notification-dispatcher")" = 'new alert notification dispatcher'
test -x "$root/current/alert-notification-dispatcher"
test -f "$root/current/web/assets/new.js"
test -f "$root/current/web/assets/old.js"
test -f "$root/current/web/.compatibility-manifests/1.assets"
test -x "$root/current/deploy/verify-customer-demo.py"
test -x "$root/current/deploy/import-source-providers.py"
test -f "$root/current/deploy/migrations/017_reconciliation_center.sql"
test -f "$root/current/deploy/migrations/036_alert_rule_archive.sql"
test -f "$root/current/deploy/migrations/038_daily_pure_hydrogen_mileage.sql"
test -f "$root/current/deploy/systemd/lingniu-vehicle-reconciliation-evaluator.timer"
test ! -e "$root/current/lingniu-vehicle-platform.service"
test "$(find "$root/releases" -mindepth 1 -maxdepth 1 -type d | wc -l | tr -d ' ')" = 3

View File

@@ -0,0 +1,81 @@
CREATE TABLE IF NOT EXISTS vehicle_open_app (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(96) NOT NULL,
app_key_hash BINARY(32) NOT NULL,
app_key_prefix CHAR(8) NOT NULL,
status VARCHAR(16) NOT NULL DEFAULT 'enabled',
valid_from DATETIME(3) NOT NULL,
valid_to DATETIME(3) NULL,
created_by VARCHAR(96) NOT NULL,
updated_by VARCHAR(96) NOT NULL,
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
UNIQUE KEY uk_vehicle_open_app_key_hash (app_key_hash),
INDEX idx_vehicle_open_app_validity (status, valid_from, valid_to),
CONSTRAINT chk_vehicle_open_app_status CHECK (status IN ('enabled', 'disabled')),
CONSTRAINT chk_vehicle_open_app_validity CHECK (valid_to IS NULL OR valid_to > valid_from)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS vehicle_open_app_vehicle (
app_id BIGINT UNSIGNED NOT NULL,
vin VARCHAR(64) NOT NULL,
valid_from DATETIME(3) NOT NULL,
valid_to DATETIME(3) NULL,
granted_by VARCHAR(96) NOT NULL,
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
PRIMARY KEY (app_id, vin),
INDEX idx_vehicle_open_grant_vin_time (vin, valid_from, valid_to),
INDEX idx_vehicle_open_grant_app_time (app_id, valid_from, valid_to, vin),
CONSTRAINT fk_vehicle_open_grant_app FOREIGN KEY (app_id) REFERENCES vehicle_open_app(id) ON DELETE CASCADE,
CONSTRAINT chk_vehicle_open_grant_validity CHECK (valid_to IS NULL OR valid_to > valid_from)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS vehicle_open_daily_energy (
vin VARCHAR(64) NOT NULL,
stat_date DATE NOT NULL,
energy_type VARCHAR(24) NOT NULL,
source_endpoint VARCHAR(128) NOT NULL DEFAULT '',
consumption_kg DECIMAL(18,3) NOT NULL DEFAULT 0,
unit VARCHAR(16) NOT NULL,
first_mass_kg DECIMAL(18,3) NULL,
last_mass_kg DECIMAL(18,3) NULL,
sample_count BIGINT UNSIGNED NOT NULL DEFAULT 0,
refuel_count INT UNSIGNED NOT NULL DEFAULT 0,
quality_status VARCHAR(24) NOT NULL,
quality_reason VARCHAR(255) NOT NULL DEFAULT '',
calculated_at DATETIME(3) NOT NULL,
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
PRIMARY KEY (vin, stat_date, energy_type),
INDEX idx_vehicle_open_energy_date_type (stat_date, energy_type, quality_status, vin),
CONSTRAINT chk_vehicle_open_energy_type CHECK (energy_type IN ('HYDROGEN')),
CONSTRAINT chk_vehicle_open_energy_quality CHECK (quality_status IN ('OK', 'NO_DATA', 'SUSPECT'))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS vehicle_open_api_audit (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
trace_id VARCHAR(64) NOT NULL,
app_id BIGINT UNSIGNED NULL,
endpoint VARCHAR(64) NOT NULL,
result VARCHAR(24) NOT NULL,
requested_vehicle_count INT UNSIGNED NOT NULL DEFAULT 0,
detail VARCHAR(512) NOT NULL DEFAULT '',
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
INDEX idx_vehicle_open_api_audit_app_time (app_id, created_at),
INDEX idx_vehicle_open_api_audit_trace (trace_id),
INDEX idx_vehicle_open_api_audit_result_time (result, created_at),
CONSTRAINT fk_vehicle_open_api_audit_app FOREIGN KEY (app_id) REFERENCES vehicle_open_app(id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS vehicle_open_admin_audit (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
app_id BIGINT UNSIGNED NULL,
actor VARCHAR(96) NOT NULL,
action VARCHAR(64) NOT NULL,
detail_json JSON NULL,
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
INDEX idx_vehicle_open_admin_audit_app_time (app_id, created_at),
INDEX idx_vehicle_open_admin_audit_actor_time (actor, created_at),
CONSTRAINT fk_vehicle_open_admin_audit_app FOREIGN KEY (app_id) REFERENCES vehicle_open_app(id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

View File

@@ -0,0 +1,65 @@
CREATE TABLE IF NOT EXISTS vehicle_open_user (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(64) NOT NULL,
display_name VARCHAR(96) NOT NULL,
password_hash VARCHAR(255) NOT NULL,
status VARCHAR(16) NOT NULL DEFAULT 'enabled',
valid_from DATETIME(3) NOT NULL,
valid_to DATETIME(3) NULL,
failed_login_count INT UNSIGNED NOT NULL DEFAULT 0,
locked_until DATETIME(3) NULL,
password_changed_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
last_login_at DATETIME(3) NULL,
created_by VARCHAR(96) NOT NULL,
updated_by VARCHAR(96) NOT NULL,
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
UNIQUE KEY uk_vehicle_open_user_username (username),
INDEX idx_vehicle_open_user_status_validity (status, valid_from, valid_to),
CONSTRAINT chk_vehicle_open_user_status CHECK (status IN ('enabled', 'disabled')),
CONSTRAINT chk_vehicle_open_user_validity CHECK (valid_to IS NULL OR valid_to > valid_from)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS vehicle_open_user_app (
user_id BIGINT UNSIGNED NOT NULL,
app_id BIGINT UNSIGNED NOT NULL,
role VARCHAR(16) NOT NULL,
granted_by VARCHAR(96) NOT NULL,
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
PRIMARY KEY (user_id, app_id),
INDEX idx_vehicle_open_user_app_app (app_id, role, user_id),
CONSTRAINT fk_vehicle_open_user_app_user FOREIGN KEY (user_id) REFERENCES vehicle_open_user(id) ON DELETE CASCADE,
CONSTRAINT fk_vehicle_open_user_app_app FOREIGN KEY (app_id) REFERENCES vehicle_open_app(id) ON DELETE CASCADE,
CONSTRAINT chk_vehicle_open_user_app_role CHECK (role IN ('owner', 'developer', 'viewer'))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS vehicle_open_user_session (
id CHAR(32) NOT NULL PRIMARY KEY,
user_id BIGINT UNSIGNED NOT NULL,
token_hash BINARY(32) NOT NULL,
issued_at DATETIME(3) NOT NULL,
expires_at DATETIME(3) NOT NULL,
last_seen_at DATETIME(3) NOT NULL,
revoked_at DATETIME(3) NULL,
remote_addr VARCHAR(96) NOT NULL DEFAULT '',
user_agent VARCHAR(255) NOT NULL DEFAULT '',
UNIQUE KEY uk_vehicle_open_user_session_token (token_hash),
INDEX idx_vehicle_open_user_session_user (user_id, expires_at),
INDEX idx_vehicle_open_user_session_expiry (expires_at, revoked_at),
CONSTRAINT fk_vehicle_open_user_session_user FOREIGN KEY (user_id) REFERENCES vehicle_open_user(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS vehicle_open_user_audit (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
user_id BIGINT UNSIGNED NULL,
actor VARCHAR(96) NOT NULL,
action VARCHAR(64) NOT NULL,
result VARCHAR(24) NOT NULL,
detail_json JSON NULL,
remote_addr VARCHAR(96) NOT NULL DEFAULT '',
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
INDEX idx_vehicle_open_user_audit_user_time (user_id, created_at),
INDEX idx_vehicle_open_user_audit_action_time (action, created_at),
CONSTRAINT fk_vehicle_open_user_audit_user FOREIGN KEY (user_id) REFERENCES vehicle_open_user(id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

View File

@@ -0,0 +1,14 @@
ALTER TABLE vehicle_open_user_session
DROP FOREIGN KEY fk_vehicle_open_user_session_user;
ALTER TABLE vehicle_open_user_session
MODIFY COLUMN user_id BIGINT UNSIGNED NULL,
ADD COLUMN platform_user_id BIGINT UNSIGNED NULL AFTER user_id,
ADD INDEX idx_vehicle_open_user_session_platform_user (platform_user_id, expires_at),
ADD CONSTRAINT fk_vehicle_open_user_session_user
FOREIGN KEY (user_id) REFERENCES vehicle_open_user(id) ON DELETE CASCADE,
ADD CONSTRAINT fk_vehicle_open_user_session_platform_user
FOREIGN KEY (platform_user_id) REFERENCES platform_user(id) ON DELETE CASCADE,
ADD CONSTRAINT chk_vehicle_open_user_session_identity
CHECK ((user_id IS NOT NULL AND platform_user_id IS NULL)
OR (user_id IS NULL AND platform_user_id IS NOT NULL));

View File

@@ -0,0 +1,21 @@
CREATE TABLE IF NOT EXISTS vehicle_open_hydrogen_stream_state (
vin VARCHAR(64) NOT NULL,
stat_date DATE NOT NULL,
source_endpoint VARCHAR(128) NOT NULL DEFAULT '',
first_mass_kg DECIMAL(18,3) NOT NULL,
last_mass_kg DECIMAL(18,3) NOT NULL,
consumption_kg DECIMAL(18,3) NOT NULL DEFAULT 0,
sample_count BIGINT UNSIGNED NOT NULL DEFAULT 1,
refuel_count INT UNSIGNED NOT NULL DEFAULT 0,
abnormal_drop_count INT UNSIGNED NOT NULL DEFAULT 0,
first_event_time DATETIME(3) NOT NULL,
last_event_time DATETIME(3) NOT NULL,
last_event_id VARCHAR(128) NOT NULL DEFAULT '',
quality_status VARCHAR(24) NOT NULL DEFAULT 'NO_DATA',
quality_reason VARCHAR(255) NOT NULL DEFAULT '有效车载氢量样本不足2条',
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
PRIMARY KEY (vin, stat_date, source_endpoint),
INDEX idx_hydrogen_stream_date_quality (stat_date, quality_status, vin),
CONSTRAINT chk_hydrogen_stream_quality CHECK (quality_status IN ('OK', 'NO_DATA', 'SUSPECT'))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

View File

@@ -0,0 +1,18 @@
CREATE TABLE IF NOT EXISTS vehicle_open_hydrogen_rate_stream_state (
vin VARCHAR(64) NOT NULL,
stat_date DATE NOT NULL,
source_endpoint VARCHAR(128) NOT NULL DEFAULT '',
first_rate_kg_per_100km DECIMAL(18,3) NOT NULL,
last_rate_kg_per_100km DECIMAL(18,3) NOT NULL,
sample_count BIGINT UNSIGNED NOT NULL DEFAULT 1,
first_event_time DATETIME(3) NOT NULL,
last_event_time DATETIME(3) NOT NULL,
last_event_id VARCHAR(128) NOT NULL DEFAULT '',
quality_status VARCHAR(24) NOT NULL DEFAULT 'NO_DATA',
quality_reason VARCHAR(255) NOT NULL DEFAULT '有效百公里氢耗样本不足2条',
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
PRIMARY KEY (vin, stat_date, source_endpoint),
INDEX idx_hydrogen_rate_stream_date_quality (stat_date, quality_status, vin),
CONSTRAINT chk_hydrogen_rate_stream_quality CHECK (quality_status IN ('OK', 'NO_DATA'))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

View File

@@ -0,0 +1,26 @@
CREATE TABLE IF NOT EXISTS vehicle_hydrogen_tank_capacity (
vin VARCHAR(64) NOT NULL,
source_vehicle_id BIGINT NOT NULL,
source_model_id BIGINT NULL,
plate_number VARCHAR(50) NOT NULL DEFAULT '',
brand_name VARCHAR(500) NOT NULL DEFAULT '',
model_name VARCHAR(500) NOT NULL DEFAULT '',
tank_capacity_l DECIMAL(12,2) NOT NULL,
active TINYINT(1) NOT NULL DEFAULT 1,
source_updated_at DATETIME NULL,
synced_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
PRIMARY KEY (vin),
INDEX idx_hydrogen_capacity_model (source_model_id),
INDEX idx_hydrogen_capacity_active (active,vin)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
ALTER TABLE vehicle_open_hydrogen_stream_state
ADD COLUMN tank_capacity_l DECIMAL(12,2) NULL AFTER abnormal_drop_count,
ADD COLUMN first_pressure_mpa DECIMAL(10,3) NULL AFTER tank_capacity_l,
ADD COLUMN last_pressure_mpa DECIMAL(10,3) NULL AFTER first_pressure_mpa,
ADD COLUMN first_temperature_c DECIMAL(10,3) NULL AFTER last_pressure_mpa,
ADD COLUMN last_temperature_c DECIMAL(10,3) NULL AFTER first_temperature_c,
ADD COLUMN calculation_method VARCHAR(32) NOT NULL DEFAULT 'PRESSURE_NIST' AFTER last_temperature_c;
CREATE INDEX idx_hydrogen_capacity_sync
ON vehicle_hydrogen_tank_capacity (synced_at,active);

View File

@@ -0,0 +1,6 @@
ALTER TABLE vehicle_open_hydrogen_stream_state
ADD COLUMN cycle_min_mass_kg DECIMAL(18,3) NULL AFTER last_mass_kg;
UPDATE vehicle_open_hydrogen_stream_state
SET cycle_min_mass_kg=last_mass_kg
WHERE cycle_min_mass_kg IS NULL;

View File

@@ -0,0 +1,22 @@
-- Add first-class automation trigger semantics without breaking existing metric rules.
ALTER TABLE vehicle_alert_rule
ADD COLUMN trigger_type VARCHAR(24) NOT NULL DEFAULT 'metric' AFTER description,
ADD COLUMN fence_name VARCHAR(80) NOT NULL DEFAULT '' AFTER trigger_type,
ADD COLUMN fence_longitude DOUBLE NOT NULL DEFAULT 0 AFTER fence_name,
ADD COLUMN fence_latitude DOUBLE NOT NULL DEFAULT 0 AFTER fence_longitude,
ADD COLUMN fence_radius_m DOUBLE NOT NULL DEFAULT 0 AFTER fence_latitude;
ALTER TABLE vehicle_alert_event
ADD COLUMN trigger_type VARCHAR(24) NOT NULL DEFAULT 'metric' AFTER severity;
CREATE INDEX idx_alert_rule_trigger_type ON vehicle_alert_rule(trigger_type, enabled);
CREATE INDEX idx_alert_event_trigger_type ON vehicle_alert_event(trigger_type, triggered_at);
-- Preserve the business meaning of rules that existed before trigger_type.
UPDATE vehicle_alert_rule
SET trigger_type = CASE
WHEN metric = 'freshness_sec' THEN 'offline'
WHEN metric = 'speed_kmh' AND operator IN ('lt','lte') AND duration_sec >= 300 THEN 'stationary'
ELSE 'metric'
END
WHERE trigger_type = 'metric';

View File

@@ -0,0 +1,7 @@
ALTER TABLE vehicle_reconciliation_issue
ADD COLUMN assignee VARCHAR(128) NOT NULL DEFAULT '' AFTER resolved_by,
ADD COLUMN assigned_by VARCHAR(128) NOT NULL DEFAULT '' AFTER assignee,
ADD COLUMN assigned_at DATETIME(3) NULL AFTER assigned_by,
ADD COLUMN due_at DATETIME(3) NULL AFTER assigned_at,
ADD INDEX idx_reconciliation_owner (assignee, status, due_at),
ADD INDEX idx_reconciliation_due (due_at, status);

View File

@@ -0,0 +1,29 @@
CREATE TABLE IF NOT EXISTS vehicle_open_mileage_snapshot (
snapshot_id CHAR(32) CHARACTER SET ascii COLLATE ascii_bin NOT NULL PRIMARY KEY,
app_id BIGINT UNSIGNED NOT NULL,
request_hash BINARY(32) NOT NULL,
start_date DATE NOT NULL,
end_date DATE NOT NULL,
vehicle_count INT UNSIGNED NOT NULL,
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
expires_at DATETIME(3) NOT NULL,
INDEX idx_open_mileage_snapshot_expiry (expires_at),
INDEX idx_open_mileage_snapshot_app (app_id, created_at),
CONSTRAINT fk_open_mileage_snapshot_app
FOREIGN KEY (app_id) REFERENCES vehicle_open_app(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS vehicle_open_mileage_snapshot_vehicle (
snapshot_id CHAR(32) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
ordinal INT UNSIGNED NOT NULL,
vin VARCHAR(64) NOT NULL,
plate VARCHAR(64) NOT NULL,
PRIMARY KEY (snapshot_id, ordinal),
UNIQUE KEY uk_open_mileage_snapshot_plate (snapshot_id, plate),
INDEX idx_open_mileage_snapshot_vin (snapshot_id, vin),
CONSTRAINT fk_open_mileage_snapshot_vehicle
FOREIGN KEY (snapshot_id) REFERENCES vehicle_open_mileage_snapshot(snapshot_id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE INDEX idx_daily_mileage_source_open_selected
ON vehicle_daily_mileage_source(vin, stat_date, protocol, is_selected, latest_event_time);

View File

@@ -0,0 +1,28 @@
ALTER TABLE vehicle_alert_notification
ADD COLUMN recipient VARCHAR(160) NOT NULL DEFAULT '',
ADD COLUMN attempt_count INT NOT NULL DEFAULT 1,
ADD COLUMN provider_message_id VARCHAR(160) NOT NULL DEFAULT '',
ADD COLUMN delivered_at DATETIME(3) NULL,
ADD COLUMN last_error VARCHAR(500) NOT NULL DEFAULT '',
ADD COLUMN last_attempt_at DATETIME(3) NULL,
ADD COLUMN retry_requested_by VARCHAR(128) NOT NULL DEFAULT '',
ADD COLUMN retry_requested_at DATETIME(3) NULL,
ADD KEY idx_alert_notification_delivery (delivery_status, created_at),
ADD KEY idx_alert_notification_retry (retry_requested_at, id);
CREATE TABLE IF NOT EXISTS vehicle_alert_notification_retry_audit (
id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
notification_id BIGINT NOT NULL,
idempotency_key VARCHAR(96) NOT NULL,
actor VARCHAR(128) NOT NULL,
reason VARCHAR(200) NOT NULL,
previous_status VARCHAR(32) NOT NULL,
next_status VARCHAR(32) NOT NULL,
attempt_count INT NOT NULL,
requested_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
UNIQUE KEY uk_alert_notification_retry_key (idempotency_key),
KEY idx_alert_notification_retry_notification (notification_id, requested_at),
CONSTRAINT fk_alert_notification_retry_notification
FOREIGN KEY (notification_id) REFERENCES vehicle_alert_notification(id)
ON DELETE CASCADE
);

View File

@@ -0,0 +1,19 @@
ALTER TABLE vehicle_alert_rule
ADD COLUMN notification_targets_json TEXT NULL AFTER notification_channels_json;
UPDATE vehicle_alert_rule
SET notification_targets_json = CASE
WHEN JSON_CONTAINS(notification_channels_json, '"in_app"') THEN
'[{"channel":"in_app","recipientId":"platform-operators","label":"平台值班组"}]'
ELSE '[]'
END
WHERE notification_targets_json IS NULL;
ALTER TABLE vehicle_alert_rule
MODIFY COLUMN notification_targets_json TEXT NOT NULL;
ALTER TABLE vehicle_alert_notification
ADD COLUMN recipient_ref VARCHAR(96) NOT NULL DEFAULT '' AFTER recipient,
ADD COLUMN lease_token VARCHAR(96) NOT NULL DEFAULT '',
ADD COLUMN lease_expires_at DATETIME(3) NULL,
ADD KEY idx_alert_notification_dispatch (channel, delivery_status, lease_expires_at, id);

View File

@@ -0,0 +1,9 @@
-- Reversible lifecycle for large automation-rule libraries.
ALTER TABLE vehicle_alert_rule
ADD COLUMN archived_by VARCHAR(128) NOT NULL DEFAULT '' AFTER updated_by,
ADD COLUMN archived_at DATETIME(3) NULL AFTER archived_by,
ADD COLUMN archive_reason VARCHAR(200) NOT NULL DEFAULT '' AFTER archived_at,
ADD KEY idx_alert_rule_lifecycle (archived_at, enabled, updated_at);
ALTER TABLE vehicle_alert_rule_audit
ADD COLUMN reason VARCHAR(200) NOT NULL DEFAULT '' AFTER action;

View File

@@ -0,0 +1,6 @@
ALTER TABLE vehicle_reconciliation_issue
ADD COLUMN archived_at DATETIME(3) NULL AFTER due_at,
ADD COLUMN archived_by VARCHAR(128) NOT NULL DEFAULT '' AFTER archived_at,
ADD COLUMN archive_reason VARCHAR(500) NOT NULL DEFAULT '' AFTER archived_by,
ADD INDEX idx_reconciliation_archive (archived_at, severity, last_seen_at),
ADD INDEX idx_reconciliation_archive_actor (archived_by, archived_at);

View File

@@ -0,0 +1,8 @@
ALTER TABLE vehicle_daily_mileage
ADD COLUMN pure_hydrogen_mileage_km DECIMAL(18,3) NOT NULL DEFAULT 0 AFTER daily_mileage_km;
ALTER TABLE vehicle_daily_mileage_source
ADD COLUMN pure_hydrogen_mileage_km DECIMAL(18,3) NOT NULL DEFAULT 0 AFTER daily_mileage_km,
ADD COLUMN pure_hydrogen_sample_count BIGINT NOT NULL DEFAULT 0 AFTER sample_count,
ADD COLUMN latest_pure_hydrogen_active TINYINT(1) NOT NULL DEFAULT 0 AFTER latest_event_time,
ADD COLUMN latest_pure_hydrogen_mode_known TINYINT(1) NOT NULL DEFAULT 0 AFTER latest_pure_hydrogen_active;

View File

@@ -0,0 +1,15 @@
CREATE TABLE IF NOT EXISTS platform_user_business_scope (
user_id BIGINT UNSIGNED NOT NULL PRIMARY KEY,
scope_level VARCHAR(24) NOT NULL,
department_ids VARCHAR(1024) NOT NULL DEFAULT '',
responsible_user_id VARCHAR(96) NOT NULL DEFAULT '',
enabled TINYINT(1) NOT NULL DEFAULT 1,
source_system VARCHAR(32) NOT NULL DEFAULT 'oneos',
source_updated_at DATETIME(3) NULL,
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
CONSTRAINT fk_platform_user_business_scope_user FOREIGN KEY (user_id) REFERENCES platform_user(id) ON DELETE CASCADE,
CONSTRAINT chk_platform_user_business_scope_level CHECK (scope_level IN ('department', 'responsible')),
INDEX idx_platform_user_business_scope_level (scope_level, enabled),
INDEX idx_platform_user_business_scope_responsible (responsible_user_id, enabled)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

View File

@@ -0,0 +1,2 @@
CREATE INDEX idx_daily_mileage_open_latest
ON vehicle_daily_mileage(vin, protocol, stat_date);

View File

@@ -0,0 +1,23 @@
[Unit]
Description=Lingniu Vehicle Alert Notification Dispatcher
After=network-online.target lingniu-vehicle-platform.service
Wants=network-online.target
[Service]
Type=simple
WorkingDirectory=/opt/lingniu-vehicle-platform/current
EnvironmentFile=/opt/lingniu-go-native/env/base.env
EnvironmentFile=/opt/lingniu-vehicle-platform/env/platform.env
ExecStart=/opt/lingniu-vehicle-platform/current/alert-notification-dispatcher
Restart=always
RestartSec=3
TimeoutStopSec=30
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=full
ProtectHome=true
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
LimitNOFILE=65536
[Install]
WantedBy=multi-user.target

View File

@@ -0,0 +1,24 @@
[Unit]
Description=Lingniu Vehicle Data Open Platform
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
WorkingDirectory=/opt/lingniu-vehicle-open-platform/current
EnvironmentFile=/opt/lingniu-go-native/env/base.env
EnvironmentFile=/opt/lingniu-vehicle-open-platform/env/open-platform.env
ExecStart=/opt/lingniu-vehicle-open-platform/current/open-platform-api
Restart=always
RestartSec=3
LimitNOFILE=1048576
KillSignal=SIGTERM
TimeoutStopSec=30
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=full
ProtectHome=true
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
[Install]
WantedBy=multi-user.target

View File

@@ -0,0 +1,24 @@
[Unit]
Description=Lingniu Vehicle Open Platform Daily Statistics
After=network-online.target
Wants=network-online.target
[Service]
Type=oneshot
WorkingDirectory=/opt/lingniu-vehicle-platform/current
EnvironmentFile=/opt/lingniu-go-native/env/base.env
EnvironmentFile=/opt/lingniu-vehicle-platform/env/platform.env
Environment=OPEN_STAT_LOOKBACK_DAYS=2
ExecStart=/opt/lingniu-vehicle-platform/current/open-platform-stat
Nice=10
IOSchedulingClass=best-effort
IOSchedulingPriority=6
TimeoutStartSec=1800
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=full
ProtectHome=true
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
[Install]
WantedBy=multi-user.target

View File

@@ -0,0 +1,12 @@
[Unit]
Description=Calculate vehicle open-platform daily statistics
[Timer]
OnCalendar=*-*-* 01:15:00
RandomizedDelaySec=10m
AccuracySec=1m
Persistent=true
Unit=lingniu-vehicle-open-stat.service
[Install]
WantedBy=timers.target