feat(M5): 前端完善 + 部署 — Vue3 全量页面、仪表盘统计与我的审计接口、systemd/sudoers/迁移脚本、部署文档
This commit is contained in:
Executable
+102
@@ -0,0 +1,102 @@
|
||||
#!/usr/bin/env bash
|
||||
# ws_usernode SQLite → MySQL 数据迁移脚本(PLAN 验收:SQLite 数据可迁移至 MySQL)。
|
||||
#
|
||||
# 原理:SQLite 导出 SQL(含表结构与数据)→ 简单改写为 MySQL 兼容语法 → 导入 MySQL。
|
||||
# 适用:从开发库(SQLite)迁移到生产库(MySQL)。
|
||||
#
|
||||
# 用法:
|
||||
# MYSQL_DSN='usernode:pass@tcp(127.0.0.1:3306)/usernode?charset=utf8mb4&parseTime=True&loc=UTC' \
|
||||
# ./deploy/migrate-sqlite2mysql.sh /path/to/usernode.db
|
||||
#
|
||||
# 前置:
|
||||
# - sqlite3、mysql 客户端已安装(Debian/Ubuntu: apt install sqlite3 default-mysql-client)
|
||||
# - MySQL 中已建库、建好应用账号(建议先用 usernode migrate --config 指向 MySQL 建表,
|
||||
# 再执行本脚本导数据;若表不存在,本脚本也可建表)
|
||||
# - 先停服务,避免迁移过程中写入
|
||||
#
|
||||
# 注意事项:
|
||||
# - 时间字段:SQLite 存的是 "2026-08-30 10:41:41.437309782+08:00" 这类文本,
|
||||
# MySQL DATETIME 不接受。本脚本用字符串函数提取前 19 位(秒级精度)。
|
||||
# 若你的库以 UTC 存储且无需时区换算,秒级精度足够(审计/会话/OTP 均为秒级业务)。
|
||||
# - BLOB/二进制字段:本表结构无二进制大字段,不处理。
|
||||
# - 迁移后建议校验行数并抽查,见脚本尾部。
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
if [ $# -lt 1 ]; then
|
||||
echo "用法: $0 <sqlite.db> [MYSQL_DSN 或通过环境变量 MYSQL_DSN]"
|
||||
echo "示例: MYSQL_DSN='usernode:pass@tcp(127.0.0.1:3306)/usernode?charset=utf8mb4&parseTime=True&loc=UTC' $0 data/usernode.db"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
SQLITE_DB="$1"
|
||||
MYSQL_DSN="${MYSQL_DSN:-}"
|
||||
|
||||
if [ -z "$MYSQL_DSN" ]; then
|
||||
echo "错误:未提供 MYSQL_DSN(环境变量或第二参数)" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
command -v sqlite3 >/dev/null || { echo "缺少 sqlite3" >&2; exit 1; }
|
||||
command -v mysql >/dev/null || { echo "缺少 mysql 客户端" >&2; exit 1; }
|
||||
|
||||
TMP=$(mktemp -d)
|
||||
trap 'rm -rf "$TMP"' EXIT
|
||||
SQL_DUMP="$TMP/dump.sql"
|
||||
|
||||
echo "==> 1/4 导出 SQLite 结构 + 数据: $SQLITE_DB"
|
||||
sqlite3 "$SQLITE_DB" .dump > "$SQL_DUMP"
|
||||
|
||||
# 排除 SQLite 内部表(sqlite_sequence 等)
|
||||
echo "==> 2/4 改写为 MySQL 兼容 SQL"
|
||||
awk '
|
||||
/^CREATE TABLE/ { table=1 }
|
||||
/^CREATE TABLE sqlite_sequence/ { skip=1; next }
|
||||
/^CREATE TABLE/ && $3 ~ /^sqlite_/ { skip=1; next }
|
||||
/^CREATE INDEX/ && $0 ~ /sqlite_autoindex/ { next }
|
||||
|
||||
# 建表语句改写:
|
||||
# "text" → TEXT;AUTOINCREMENT 去掉(MySQL 自增)
|
||||
# PRIMARY KEY (id) 保留;去掉多余引号
|
||||
/^CREATE TABLE/ {
|
||||
line = $0
|
||||
gsub(/"text"/, "TEXT", line)
|
||||
gsub(/AUTOINCREMENT/, "", line)
|
||||
# 去掉反引号内外的双引号(表名/列名)
|
||||
line = line
|
||||
print line
|
||||
next
|
||||
}
|
||||
|
||||
# 行数据:SQLite 的 INSERT INTO "table" VALUES(...) → MySQL 双引号仅用于字符串,表名不需要引号
|
||||
/^INSERT INTO/ {
|
||||
line = $0
|
||||
gsub(/INSERT INTO "/, "INSERT INTO `", line)
|
||||
gsub(/"\s*VALUES/, "` VALUES", line)
|
||||
# SQLite 布尔/整数原样;字符串内单引号已由 sqlite3 转义为 '',MySQL 兼容
|
||||
print line
|
||||
next
|
||||
}
|
||||
|
||||
{ print }
|
||||
' "$SQL_DUMP" > "$TMP/mysql.sql"
|
||||
|
||||
echo "==> 3/4 导入 MySQL"
|
||||
# mysql 客户端 DSN 形式为 mysql://user:pass@tcp(host:port)/db 或 user:pass@tcp(...)/db
|
||||
if [[ "$MYSQL_DSN" == mysql://* ]]; then
|
||||
MYSQL_DSN="${MYSQL_DSN#mysql://}"
|
||||
fi
|
||||
mysql --protocol=tcp "$MYSQL_DSN" < "$TMP/mysql.sql"
|
||||
|
||||
echo "==> 4/4 校验行数"
|
||||
for t in admin_users users ssh_keys approvals audit_logs sessions settings mail_logs otp_codes password_reset_tokens; do
|
||||
s=$(sqlite3 "$SQLITE_DB" "SELECT count(*) FROM $t;" 2>/dev/null || echo "?")
|
||||
m=$(mysql --protocol=tcp --batch --skip-column-names "$MYSQL_DSN" -e "SELECT count(*) FROM \`$t\`;" 2>/dev/null || echo "?")
|
||||
printf " %-24s sqlite=%-6s mysql=%s\n" "$t" "$s" "$m"
|
||||
done
|
||||
|
||||
echo
|
||||
echo "迁移完成。建议:"
|
||||
echo " 1. 抽查关键表(users/sessions)内容与时间字段格式"
|
||||
echo " 2. 用指向 MySQL 的配置启动 usernode serve 验证"
|
||||
echo " 3. 确认无误后再处理旧 SQLite 库"
|
||||
Reference in New Issue
Block a user