diff --git a/scripts/manual_db_release/01_create_database.sql b/scripts/manual_db_release/01_create_database.sql new file mode 100644 index 0000000..8cef1a6 --- /dev/null +++ b/scripts/manual_db_release/01_create_database.sql @@ -0,0 +1,25 @@ +-- 生产数据库手工创建模板 +-- 用途:首次上线时手工创建数据库和应用账号 +-- 注意:执行前请先替换所有占位符 +-- +-- 占位符: +-- __DB_NAME__ 生产数据库名 +-- __DB_USER__ 生产数据库用户名 +-- __DB_PASSWORD__ 生产数据库密码 + +-- 1. 创建应用数据库账号 +CREATE ROLE junhong_cmp +LOGIN +PASSWORD 'CtCom1zBzPQbpVNf3rCNxH'; + +-- 2. 创建生产数据库 +CREATE DATABASE junhong_cmp_prod +OWNER junhong_cmp +ENCODING 'UTF8' +TEMPLATE template0; + +-- 3. 授权 +GRANT ALL PRIVILEGES ON DATABASE junhong_cmp_prod TO junhong_cmp; + +-- 4. 设置默认时区(与应用一致) +ALTER DATABASE junhong_cmp_prod SET timezone TO 'Asia/Shanghai'; diff --git a/scripts/manual_db_release/02_generate_schema_baseline.sh b/scripts/manual_db_release/02_generate_schema_baseline.sh new file mode 100755 index 0000000..c6e0a5a --- /dev/null +++ b/scripts/manual_db_release/02_generate_schema_baseline.sh @@ -0,0 +1,76 @@ +#!/bin/bash +# 生成生产首发 schema 基线(只读,不修改源库) +# +# 用法: +# SOURCE_DSN="postgres://user:pass@host:5432/db?sslmode=disable" \ +# OUTPUT_SQL="/tmp/00_schema_baseline.sql" \ +# bash scripts/manual_db_release/02_generate_schema_baseline.sh +# +# 可选: +# EXTRA_EXCLUDE_TABLES="table_a,table_b" +# +# 默认排除: +# - schema_migrations +# - tb_data_usage_record +# - tb_card_replacement_record_legacy + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +OUTPUT_SQL="${OUTPUT_SQL:-${SCRIPT_DIR}/generated/00_schema_baseline.sql}" + +if ! command -v pg_dump >/dev/null 2>&1; then + echo "错误:未找到 pg_dump,请先安装 PostgreSQL 客户端工具。" + exit 1 +fi + +if [ -z "${SOURCE_DSN:-}" ]; then + echo "错误:必须设置 SOURCE_DSN。" + exit 1 +fi + +mkdir -p "$(dirname "${OUTPUT_SQL}")" + +exclude_tables=( + "schema_migrations" + "tb_data_usage_record" + "tb_card_replacement_record_legacy" +) + +if [ -n "${EXTRA_EXCLUDE_TABLES:-}" ]; then + IFS=',' read -r -a extra_tables <<< "${EXTRA_EXCLUDE_TABLES}" + for table_name in "${extra_tables[@]}"; do + table_name="$(echo "${table_name}" | xargs)" + if [ -n "${table_name}" ]; then + exclude_tables+=("${table_name}") + fi + done +fi + +pg_dump_args=( + "--schema-only" + "--no-owner" + "--no-acl" + "--quote-all-identifiers" +) + +for table_name in "${exclude_tables[@]}"; do + pg_dump_args+=("--exclude-table=${table_name}") +done + +echo "开始生成 schema 基线:${OUTPUT_SQL}" +pg_dump "${pg_dump_args[@]}" "${SOURCE_DSN}" > "${OUTPUT_SQL}" + +echo "" +echo "schema 基线生成完成。" +echo "默认已排除以下对象:" +for table_name in "${exclude_tables[@]}"; do + echo " - ${table_name}" +done + +echo "" +echo "建议你人工复核以下兼容保留表是否需要继续保留:" +echo " - tb_card_replacement_request" +echo " - tb_number_card" +echo " - tb_payment_merchant_setting" +echo " - tb_dev_capability_config" diff --git a/scripts/manual_db_release/03_export_base_data_from_source.sh b/scripts/manual_db_release/03_export_base_data_from_source.sh new file mode 100755 index 0000000..6bf515b --- /dev/null +++ b/scripts/manual_db_release/03_export_base_data_from_source.sh @@ -0,0 +1,80 @@ +#!/bin/bash +# 从已确认的源库导出基础数据(只读,不修改源库) +# +# 用法: +# SOURCE_DSN="postgres://user:pass@host:5432/db?sslmode=disable" \ +# bash scripts/manual_db_release/03_export_base_data_from_source.sh +# +# 如需导出敏感表: +# SOURCE_DSN="..." INCLUDE_SENSITIVE=1 bash scripts/manual_db_release/03_export_base_data_from_source.sh + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +GENERATED_DIR="${SCRIPT_DIR}/generated" +NON_SENSITIVE_DIR="${GENERATED_DIR}/non_sensitive" +SENSITIVE_DIR="${GENERATED_DIR}/sensitive" + +if ! command -v pg_dump >/dev/null 2>&1; then + echo "错误:未找到 pg_dump,请先安装 PostgreSQL 客户端工具。" + exit 1 +fi + +if [ -z "${SOURCE_DSN:-}" ]; then + echo "错误:必须设置 SOURCE_DSN。" + exit 1 +fi + +mkdir -p "${NON_SENSITIVE_DIR}" "${SENSITIVE_DIR}" + +non_sensitive_tables=( + "tb_permission" + "tb_role" + "tb_role_permission" + "tb_carrier" + "tb_commission_withdrawal_setting" + "tb_polling_config" + "tb_polling_concurrency_config" + "tb_polling_alert_rule" + "tb_data_cleanup_config" +) + +sensitive_tables=( + "tb_wechat_config" +) + +dump_table() { + local table_name="$1" + local output_file="$2" + + pg_dump \ + --data-only \ + --column-inserts \ + --no-owner \ + --no-acl \ + --quote-all-identifiers \ + --table="${table_name}" \ + "${SOURCE_DSN}" > "${output_file}" +} + +echo "开始导出非敏感基础数据..." +for table_name in "${non_sensitive_tables[@]}"; do + output_file="${NON_SENSITIVE_DIR}/${table_name}.sql" + dump_table "${table_name}" "${output_file}" + echo " 已导出: ${table_name}" +done + +if [ "${INCLUDE_SENSITIVE:-0}" = "1" ]; then + echo "开始导出敏感基础数据..." + for table_name in "${sensitive_tables[@]}"; do + output_file="${SENSITIVE_DIR}/${table_name}.sql" + dump_table "${table_name}" "${output_file}" + echo " 已导出: ${table_name}" + done +else + echo "未导出敏感表。若需导出 tb_wechat_config,请设置 INCLUDE_SENSITIVE=1。" +fi + +echo "" +echo "导出完成,输出目录:${GENERATED_DIR}" +echo "注意:generated 目录下的 SQL 可能包含敏感信息,请勿直接提交到 Git。" diff --git a/scripts/manual_db_release/04_import_base_data_into_target.sh b/scripts/manual_db_release/04_import_base_data_into_target.sh new file mode 100755 index 0000000..7e4c868 --- /dev/null +++ b/scripts/manual_db_release/04_import_base_data_into_target.sh @@ -0,0 +1,71 @@ +#!/bin/bash +# 将已导出的基础数据 SQL 导入目标库 +# +# 用法: +# TARGET_DSN="postgres://user:pass@host:5432/db?sslmode=disable" \ +# bash scripts/manual_db_release/04_import_base_data_into_target.sh +# +# 如需导入敏感表: +# TARGET_DSN="..." INCLUDE_SENSITIVE=1 bash scripts/manual_db_release/04_import_base_data_into_target.sh + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +GENERATED_DIR="${SCRIPT_DIR}/generated" +NON_SENSITIVE_DIR="${GENERATED_DIR}/non_sensitive" +SENSITIVE_DIR="${GENERATED_DIR}/sensitive" + +if ! command -v psql >/dev/null 2>&1; then + echo "错误:未找到 psql,请先安装 PostgreSQL 客户端工具。" + exit 1 +fi + +if [ -z "${TARGET_DSN:-}" ]; then + echo "错误:必须设置 TARGET_DSN。" + exit 1 +fi + +import_order=( + "tb_permission" + "tb_role" + "tb_role_permission" + "tb_carrier" + "tb_commission_withdrawal_setting" + "tb_polling_config" + "tb_polling_concurrency_config" + "tb_polling_alert_rule" + "tb_data_cleanup_config" +) + +echo "开始导入非敏感基础数据..." +for table_name in "${import_order[@]}"; do + sql_file="${NON_SENSITIVE_DIR}/${table_name}.sql" + if [ ! -f "${sql_file}" ]; then + echo "错误:缺少导出文件 ${sql_file}" + echo "请先执行 03_export_base_data_from_source.sh" + exit 1 + fi + + echo " 导入: ${table_name}" + psql "${TARGET_DSN}" -v ON_ERROR_STOP=1 -f "${sql_file}" +done + +if [ "${INCLUDE_SENSITIVE:-0}" = "1" ]; then + sensitive_file="${SENSITIVE_DIR}/tb_wechat_config.sql" + if [ ! -f "${sensitive_file}" ]; then + echo "错误:缺少敏感导出文件 ${sensitive_file}" + echo "请先使用 INCLUDE_SENSITIVE=1 执行 03_export_base_data_from_source.sh" + exit 1 + fi + + echo " 导入: tb_wechat_config" + psql "${TARGET_DSN}" -v ON_ERROR_STOP=1 -f "${sensitive_file}" +else + echo "未导入敏感表。若需导入 tb_wechat_config,请设置 INCLUDE_SENSITIVE=1。" +fi + +echo "" +echo "基础数据导入完成。" +echo "下一步请执行:" +echo " 1. scripts/manual_db_release/05_repair_base_data_sequences.sql" +echo " 2. scripts/manual_db_release/06_mark_schema_migrations.sql" diff --git a/scripts/manual_db_release/05_repair_base_data_sequences.sql b/scripts/manual_db_release/05_repair_base_data_sequences.sql new file mode 100644 index 0000000..8240b10 --- /dev/null +++ b/scripts/manual_db_release/05_repair_base_data_sequences.sql @@ -0,0 +1,62 @@ +-- 修正基础数据导入后的自增序列 +-- 用途:避免显式插入 ID 后,后续新增数据仍从旧序列值开始 + +SELECT setval( + pg_get_serial_sequence('"tb_permission"', 'id'), + COALESCE((SELECT MAX(id) FROM tb_permission), 1), + (SELECT COUNT(*) > 0 FROM tb_permission) +); + +SELECT setval( + pg_get_serial_sequence('"tb_role"', 'id'), + COALESCE((SELECT MAX(id) FROM tb_role), 1), + (SELECT COUNT(*) > 0 FROM tb_role) +); + +SELECT setval( + pg_get_serial_sequence('"tb_role_permission"', 'id'), + COALESCE((SELECT MAX(id) FROM tb_role_permission), 1), + (SELECT COUNT(*) > 0 FROM tb_role_permission) +); + +SELECT setval( + pg_get_serial_sequence('"tb_carrier"', 'id'), + COALESCE((SELECT MAX(id) FROM tb_carrier), 1), + (SELECT COUNT(*) > 0 FROM tb_carrier) +); + +SELECT setval( + pg_get_serial_sequence('"tb_wechat_config"', 'id'), + COALESCE((SELECT MAX(id) FROM tb_wechat_config), 1), + (SELECT COUNT(*) > 0 FROM tb_wechat_config) +); + +SELECT setval( + pg_get_serial_sequence('"tb_commission_withdrawal_setting"', 'id'), + COALESCE((SELECT MAX(id) FROM tb_commission_withdrawal_setting), 1), + (SELECT COUNT(*) > 0 FROM tb_commission_withdrawal_setting) +); + +SELECT setval( + pg_get_serial_sequence('"tb_polling_config"', 'id'), + COALESCE((SELECT MAX(id) FROM tb_polling_config), 1), + (SELECT COUNT(*) > 0 FROM tb_polling_config) +); + +SELECT setval( + pg_get_serial_sequence('"tb_polling_concurrency_config"', 'id'), + COALESCE((SELECT MAX(id) FROM tb_polling_concurrency_config), 1), + (SELECT COUNT(*) > 0 FROM tb_polling_concurrency_config) +); + +SELECT setval( + pg_get_serial_sequence('"tb_polling_alert_rule"', 'id'), + COALESCE((SELECT MAX(id) FROM tb_polling_alert_rule), 1), + (SELECT COUNT(*) > 0 FROM tb_polling_alert_rule) +); + +SELECT setval( + pg_get_serial_sequence('"tb_data_cleanup_config"', 'id'), + COALESCE((SELECT MAX(id) FROM tb_data_cleanup_config), 1), + (SELECT COUNT(*) > 0 FROM tb_data_cleanup_config) +); diff --git a/scripts/manual_db_release/06_mark_schema_migrations.sql b/scripts/manual_db_release/06_mark_schema_migrations.sql new file mode 100644 index 0000000..22215c4 --- /dev/null +++ b/scripts/manual_db_release/06_mark_schema_migrations.sql @@ -0,0 +1,16 @@ +-- 手工对齐 schema_migrations +-- 场景:首次生产上线使用“手工 schema 基线”建表后, +-- 需要告诉 golang-migrate 当前数据库已经处于哪个版本。 +-- +-- 当前仓库基线版本:140 +-- 如果你上线时仓库已新增 141+ 迁移,请同步修改此文件。 + +CREATE TABLE IF NOT EXISTS schema_migrations ( + version BIGINT NOT NULL PRIMARY KEY, + dirty BOOLEAN NOT NULL +); + +TRUNCATE TABLE schema_migrations; + +INSERT INTO schema_migrations (version, dirty) +VALUES (140, FALSE); diff --git a/scripts/manual_db_release/07_verify_manual_release.sql b/scripts/manual_db_release/07_verify_manual_release.sql new file mode 100644 index 0000000..934c2f4 --- /dev/null +++ b/scripts/manual_db_release/07_verify_manual_release.sql @@ -0,0 +1,35 @@ +-- 手工上线后的数据库校验脚本 +-- 用途:确认 schema 版本、核心基础数据、敏感配置是否已正确导入 + +SELECT version, dirty +FROM schema_migrations; + +SELECT 'tb_permission' AS table_name, COUNT(*) AS row_count FROM tb_permission +UNION ALL +SELECT 'tb_role', COUNT(*) FROM tb_role +UNION ALL +SELECT 'tb_role_permission', COUNT(*) FROM tb_role_permission +UNION ALL +SELECT 'tb_carrier', COUNT(*) FROM tb_carrier +UNION ALL +SELECT 'tb_commission_withdrawal_setting', COUNT(*) FROM tb_commission_withdrawal_setting +UNION ALL +SELECT 'tb_polling_config', COUNT(*) FROM tb_polling_config +UNION ALL +SELECT 'tb_polling_concurrency_config', COUNT(*) FROM tb_polling_concurrency_config +UNION ALL +SELECT 'tb_polling_alert_rule', COUNT(*) FROM tb_polling_alert_rule +UNION ALL +SELECT 'tb_data_cleanup_config', COUNT(*) FROM tb_data_cleanup_config +UNION ALL +SELECT 'tb_wechat_config', COUNT(*) FROM tb_wechat_config; + +SELECT table_name +FROM information_schema.tables +WHERE table_schema = 'public' + AND table_name IN ('tb_data_usage_record', 'tb_card_replacement_record_legacy') +ORDER BY table_name; + +SELECT COUNT(*) AS super_admin_count +FROM tb_account +WHERE user_type = 1; diff --git a/scripts/manual_db_release/generated/.gitignore b/scripts/manual_db_release/generated/.gitignore new file mode 100644 index 0000000..d6b7ef3 --- /dev/null +++ b/scripts/manual_db_release/generated/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore