Compare commits

...

2 Commits

Author SHA1 Message Date
irving
15f058617a remove unused file
Some checks failed
Build and Push Backend / docker (push) Failing after 5s
2025-11-07 22:39:14 -05:00
irving
29ff0a2637 feat: add flyway cli wrapper and staging restore 2025-11-07 22:38:47 -05:00
9 changed files with 572 additions and 3 deletions

View File

@@ -134,6 +134,34 @@ mvn spotless:apply compile
mvn spotless:apply checkstyle:check compile
```
## 数据库迁移
仓库根目录下提供 `flyway/` 配置与 `flyway.sh` 工具脚本,用于在不同环境执行迁移。
### Profile 与配置
| Profile | 配置文件 | 用途 |
|------------|-----------------------|------------------------------|
| `dev` | `flyway/dev.conf` | 本地 `play-with` 开发库 |
| `staging` | `flyway/staging.conf` | 生产克隆 / 本地 staging 库 |
| `api-test` | `flyway/api-test.conf`| API 集成测试数据库 |
| `prod` | `flyway/prod.conf` | 线上生产库 |
### 示例
```bash
# 校验本地 schema
./flyway.sh validate --profile dev
# 对 API 测试库执行迁移
./flyway.sh migrate --profile api-test
# 修复 staging 库
./flyway.sh repair --profile staging
```
当对 `prod` profile 执行 `migrate``repair` 时,脚本会连续两次提示“你备份数据库了吗?”以避免误操作,输入 `yes` 才会继续。
## API 集成测试指南
`play-admin` 模块内提供了基于 `apitest` Profile 的端到端测试套件。为了稳定跑通所有 API 场景,请按以下步骤准备环境:

92
flyway.sh Executable file
View File

@@ -0,0 +1,92 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
CONFIG_DIR="$SCRIPT_DIR/flyway"
usage() {
cat <<USAGE
Usage: $(basename "$0") <migrate|validate|repair> --profile <api-test|dev|staging|prod>
Examples:
./flyway.sh validate --profile dev
./flyway.sh migrate --profile api-test
USAGE
exit 1
}
if [[ $# -lt 2 ]]; then
usage
fi
action="$1"
shift
case "$action" in
migrate|validate|repair) ;;
*) echo "[ERROR] Unsupported action: $action" >&2; usage ;;
esac
profile=""
while [[ $# -gt 0 ]]; do
case "$1" in
--profile|-p)
[[ $# -ge 2 ]] || usage
profile="$2"
shift 2
;;
*)
echo "[ERROR] Unknown argument: $1" >&2
usage
;;
esac
done
if [[ -z "$profile" ]]; then
usage
fi
case "$profile" in
api-test|apitest)
profile="api-test"
config_file="$CONFIG_DIR/api-test.conf"
;;
dev)
config_file="$CONFIG_DIR/dev.conf"
;;
staging)
config_file="$CONFIG_DIR/staging.conf"
;;
prod)
config_file="$CONFIG_DIR/prod.conf"
;;
*)
echo "[ERROR] Unknown profile: $profile" >&2
usage
;;
esac
if [[ ! -f "$config_file" ]]; then
echo "[ERROR] Config file not found: $config_file" >&2
exit 1
fi
confirm_backup() {
local prompt="$1"
read -r -p "$prompt (yes/no): " reply
if [[ "$reply" != "yes" ]]; then
echo "操作已取消"
exit 1
fi
}
if [[ "$profile" == "prod" && ( "$action" == "migrate" || "$action" == "repair" ) ]]; then
confirm_backup "你备份数据库了吗?"
confirm_backup "你真的备份了吗?"
fi
exec mvn \
-f "$SCRIPT_DIR/pom.xml" \
-pl play-admin \
-DskipTests \
"flyway:$action" \
"-Dflyway.configFiles=$config_file"

10
flyway/api-test.conf Normal file
View File

@@ -0,0 +1,10 @@
flyway.url=jdbc:mysql://127.0.0.1:33306/peipei_apitest?useUnicode=true&characterEncoding=utf8&characterSetResults=utf8
flyway.user=apitest
flyway.password=apitest
flyway.locations=classpath:db/migration
flyway.table=admin_flyway_schema_history
flyway.baselineOnMigrate=true
flyway.baselineVersion=1
flyway.cleanDisabled=true
flyway.outOfOrder=false
flyway.validateOnMigrate=false

View File

@@ -1,10 +1,10 @@
flyway.url=jdbc:mysql://122.51.20.105:3306/play-with?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&allowMultiQueries=true&rewriteBatchedStatements=true
flyway.user=root
flyway.password=KdaKRZ2trpdhNePa
lyway.locations=classpath:db/migration
flyway.locations=classpath:db/migration
flyway.table=admin_flyway_schema_history
flyway.baselineOnMigrate=true
flyway.baselineVersion=1
flyway.cleanDisabled=true
flyway.outOfOrder=false
flyway.validateOnMigrate=true
flyway.validateOnMigrate=true

10
flyway/staging.conf Normal file
View File

@@ -0,0 +1,10 @@
flyway.url=jdbc:mysql://127.0.0.1:3307/play-with?useUnicode=true&characterEncoding=utf8&characterSetResults=utf8
flyway.user=root
flyway.password=root
flyway.locations=classpath:db/migration
flyway.table=admin_flyway_schema_history
flyway.baselineOnMigrate=true
flyway.baselineVersion=1
flyway.cleanDisabled=true
flyway.outOfOrder=false
flyway.validateOnMigrate=false

View File

@@ -13,13 +13,14 @@ spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driverClassName: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:33306/peipei_apitest?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false&allowMultiQueries=true&rewriteBatchedStatements=true
url: jdbc:mysql://localhost:33306/peipei_apitest?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false&allowMultiQueries=true&rewriteBatchedStatements=true&connectionCollation=utf8mb4_general_ci&sessionVariables=collation_connection=utf8mb4_general_ci
username: apitest
password: apitest
druid:
enable: true
db-type: mysql
filters: stat,wall
connection-init-sqls: SET NAMES utf8mb4 COLLATE utf8mb4_general_ci
max-active: 20
initial-size: 1
max-wait: 60000

View File

@@ -0,0 +1,291 @@
-- Align legacy tables with production: ensure core order/earnings tables use utf8mb4_general_ci.
-- Safe to run multiple times; already-general_ci tables will simply rebuild without data changes.
-- Some historical tables (e.g. sys_tenant_recharge_info) still rely on zero-datetime
-- defaults. MySQL 8 with NO_ZERO_DATE / NO_ZERO_IN_DATE rejects those definitions
-- when the engine rebuilds the table. Temporarily drop those sql_mode flags so the
-- table structure matches production before/after this migration.
SET @OLD_SQL_MODE = @@sql_mode;
SET SESSION sql_mode = REPLACE(REPLACE(@OLD_SQL_MODE, 'NO_ZERO_IN_DATE', ''), 'NO_ZERO_DATE', '');
ALTER TABLE `play_order_info`
CONVERT TO CHARACTER SET utf8mb4
COLLATE utf8mb4_general_ci;
ALTER TABLE `play_order_log_info`
CONVERT TO CHARACTER SET utf8mb4
COLLATE utf8mb4_general_ci;
ALTER TABLE `play_order_random_info`
CONVERT TO CHARACTER SET utf8mb4
COLLATE utf8mb4_general_ci;
ALTER TABLE `play_order_refund_info`
CONVERT TO CHARACTER SET utf8mb4
COLLATE utf8mb4_general_ci;
ALTER TABLE `play_order_continue_info`
CONVERT TO CHARACTER SET utf8mb4
COLLATE utf8mb4_general_ci;
ALTER TABLE `play_order_demand_info`
CONVERT TO CHARACTER SET utf8mb4
COLLATE utf8mb4_general_ci;
ALTER TABLE `play_order_evaluate_info`
CONVERT TO CHARACTER SET utf8mb4
COLLATE utf8mb4_general_ci;
ALTER TABLE `play_custom_user_info`
CONVERT TO CHARACTER SET utf8mb4
COLLATE utf8mb4_general_ci;
ALTER TABLE `play_custom_gift_info`
CONVERT TO CHARACTER SET utf8mb4
COLLATE utf8mb4_general_ci;
ALTER TABLE `play_custom_article_info`
CONVERT TO CHARACTER SET utf8mb4
COLLATE utf8mb4_general_ci;
ALTER TABLE `play_custom_follow_info`
CONVERT TO CHARACTER SET utf8mb4
COLLATE utf8mb4_general_ci;
ALTER TABLE `play_custom_leave_msg`
CONVERT TO CHARACTER SET utf8mb4
COLLATE utf8mb4_general_ci;
ALTER TABLE `play_custom_level_info`
CONVERT TO CHARACTER SET utf8mb4
COLLATE utf8mb4_general_ci;
ALTER TABLE `play_clerk_user_info`
CONVERT TO CHARACTER SET utf8mb4
COLLATE utf8mb4_general_ci;
ALTER TABLE `play_clerk_user_review_info`
CONVERT TO CHARACTER SET utf8mb4
COLLATE utf8mb4_general_ci;
ALTER TABLE `play_clerk_level_info`
CONVERT TO CHARACTER SET utf8mb4
COLLATE utf8mb4_general_ci;
ALTER TABLE `play_clerk_gift_info`
CONVERT TO CHARACTER SET utf8mb4
COLLATE utf8mb4_general_ci;
ALTER TABLE `play_clerk_classification_info`
CONVERT TO CHARACTER SET utf8mb4
COLLATE utf8mb4_general_ci;
ALTER TABLE `play_clerk_type_info`
CONVERT TO CHARACTER SET utf8mb4
COLLATE utf8mb4_general_ci;
ALTER TABLE `play_clerk_type_user_info`
CONVERT TO CHARACTER SET utf8mb4
COLLATE utf8mb4_general_ci;
ALTER TABLE `play_personnel_group_info`
CONVERT TO CHARACTER SET utf8mb4
COLLATE utf8mb4_general_ci;
ALTER TABLE `play_personnel_admin_info`
CONVERT TO CHARACTER SET utf8mb4
COLLATE utf8mb4_general_ci;
ALTER TABLE `play_personnel_waiter_info`
CONVERT TO CHARACTER SET utf8mb4
COLLATE utf8mb4_general_ci;
ALTER TABLE `play_coupon_info`
CONVERT TO CHARACTER SET utf8mb4
COLLATE utf8mb4_general_ci;
ALTER TABLE `play_coupon_details`
CONVERT TO CHARACTER SET utf8mb4
COLLATE utf8mb4_general_ci;
ALTER TABLE `play_gift_info`
CONVERT TO CHARACTER SET utf8mb4
COLLATE utf8mb4_general_ci;
ALTER TABLE `sys_user`
CONVERT TO CHARACTER SET utf8mb4
COLLATE utf8mb4_general_ci;
ALTER TABLE `sys_user_role`
CONVERT TO CHARACTER SET utf8mb4
COLLATE utf8mb4_general_ci;
ALTER TABLE `sys_role`
CONVERT TO CHARACTER SET utf8mb4
COLLATE utf8mb4_general_ci;
ALTER TABLE `sys_role_menu`
CONVERT TO CHARACTER SET utf8mb4
COLLATE utf8mb4_general_ci;
ALTER TABLE `sys_role_dept`
CONVERT TO CHARACTER SET utf8mb4
COLLATE utf8mb4_general_ci;
ALTER TABLE `sys_dept`
CONVERT TO CHARACTER SET utf8mb4
COLLATE utf8mb4_general_ci;
ALTER TABLE `sys_menu`
CONVERT TO CHARACTER SET utf8mb4
COLLATE utf8mb4_general_ci;
ALTER TABLE `sys_tenant`
CONVERT TO CHARACTER SET utf8mb4
COLLATE utf8mb4_general_ci;
ALTER TABLE `sys_tenant_package`
CONVERT TO CHARACTER SET utf8mb4
COLLATE utf8mb4_general_ci;
ALTER TABLE `sys_tenant_recharge_info`
CONVERT TO CHARACTER SET utf8mb4
COLLATE utf8mb4_general_ci;
ALTER TABLE `blind_box_config`
CONVERT TO CHARACTER SET utf8mb4
COLLATE utf8mb4_general_ci;
ALTER TABLE `blind_box_pool`
CONVERT TO CHARACTER SET utf8mb4
COLLATE utf8mb4_general_ci;
ALTER TABLE `blind_box_reward`
CONVERT TO CHARACTER SET utf8mb4
COLLATE utf8mb4_general_ci;
ALTER TABLE `commodity_info`
CONVERT TO CHARACTER SET utf8mb4
COLLATE utf8mb4_general_ci;
ALTER TABLE `gen_table`
CONVERT TO CHARACTER SET utf8mb4
COLLATE utf8mb4_general_ci;
ALTER TABLE `gen_table_column`
CONVERT TO CHARACTER SET utf8mb4
COLLATE utf8mb4_general_ci;
ALTER TABLE `order_details_info`
CONVERT TO CHARACTER SET utf8mb4
COLLATE utf8mb4_general_ci;
ALTER TABLE `order_log_info`
CONVERT TO CHARACTER SET utf8mb4
COLLATE utf8mb4_general_ci;
ALTER TABLE `play_account info`
CONVERT TO CHARACTER SET utf8mb4
COLLATE utf8mb4_general_ci;
ALTER TABLE `play_avatar_frame_info`
CONVERT TO CHARACTER SET utf8mb4
COLLATE utf8mb4_general_ci;
ALTER TABLE `play_balance_details_info`
CONVERT TO CHARACTER SET utf8mb4
COLLATE utf8mb4_general_ci;
ALTER TABLE `play_clerk_article_info`
CONVERT TO CHARACTER SET utf8mb4
COLLATE utf8mb4_general_ci;
ALTER TABLE `play_clerk_commodity_info`
CONVERT TO CHARACTER SET utf8mb4
COLLATE utf8mb4_general_ci;
ALTER TABLE `play_clerk_data_review_info`
CONVERT TO CHARACTER SET utf8mb4
COLLATE utf8mb4_general_ci;
ALTER TABLE `play_clerk_operation_log`
CONVERT TO CHARACTER SET utf8mb4
COLLATE utf8mb4_general_ci;
ALTER TABLE `play_clerk_pk`
CONVERT TO CHARACTER SET utf8mb4
COLLATE utf8mb4_general_ci;
ALTER TABLE `play_clerk_ranking_info`
CONVERT TO CHARACTER SET utf8mb4
COLLATE utf8mb4_general_ci;
ALTER TABLE `play_clerk_resource info`
CONVERT TO CHARACTER SET utf8mb4
COLLATE utf8mb4_general_ci;
ALTER TABLE `play_clerk_wages_details_info`
CONVERT TO CHARACTER SET utf8mb4
COLLATE utf8mb4_general_ci;
ALTER TABLE `play_clerk_wages_info`
CONVERT TO CHARACTER SET utf8mb4
COLLATE utf8mb4_general_ci;
ALTER TABLE `play_clerk_waiter_info`
CONVERT TO CHARACTER SET utf8mb4
COLLATE utf8mb4_general_ci;
ALTER TABLE `shop_ui_setting`
CONVERT TO CHARACTER SET utf8mb4
COLLATE utf8mb4_general_ci;
ALTER TABLE `sys_administrative_area_dict_info`
CONVERT TO CHARACTER SET utf8mb4
COLLATE utf8mb4_general_ci;
ALTER TABLE `sys_dict`
CONVERT TO CHARACTER SET utf8mb4
COLLATE utf8mb4_general_ci;
ALTER TABLE `sys_dict_data`
CONVERT TO CHARACTER SET utf8mb4
COLLATE utf8mb4_general_ci;
ALTER TABLE `sys_login_log`
CONVERT TO CHARACTER SET utf8mb4
COLLATE utf8mb4_general_ci;
ALTER TABLE `sys_operation_log`
CONVERT TO CHARACTER SET utf8mb4
COLLATE utf8mb4_general_ci;
ALTER TABLE `play_shop_article_info`
CONVERT TO CHARACTER SET utf8mb4
COLLATE utf8mb4_general_ci;
ALTER TABLE `play_shop_carousel_info`
CONVERT TO CHARACTER SET utf8mb4
COLLATE utf8mb4_general_ci;
ALTER TABLE `play_commodity_info`
CONVERT TO CHARACTER SET utf8mb4
COLLATE utf8mb4_general_ci;
ALTER TABLE `play_commodity_and_level_info`
CONVERT TO CHARACTER SET utf8mb4
COLLATE utf8mb4_general_ci;
ALTER TABLE `play_custom_amount details`
CONVERT TO CHARACTER SET utf8mb4
COLLATE utf8mb4_general_ci;
ALTER TABLE `play_notice_info`
CONVERT TO CHARACTER SET utf8mb4
COLLATE utf8mb4_general_ci;
ALTER TABLE `play_order_complaint_info`
CONVERT TO CHARACTER SET utf8mb4
COLLATE utf8mb4_general_ci;
-- Restore original sql_mode flags once the rebuilds are done.
SET SESSION sql_mode = @OLD_SQL_MODE;

137
recreate-staging.sh Executable file
View File

@@ -0,0 +1,137 @@
#!/usr/bin/env bash
set -euo pipefail
CONTAINER_NAME=${STAGING_MYSQL_CONTAINER:-peipei-mysql-staging}
VOLUME_NAME=${STAGING_MYSQL_VOLUME:-peipei-mysql-staging-data}
HOST_PORT=${STAGING_MYSQL_PORT:-3307}
DB_NAME=${STAGING_MYSQL_DB:-play-with}
ROOT_PASSWORD=${STAGING_MYSQL_ROOT_PASSWORD:-root}
MYSQL_IMAGE=${STAGING_MYSQL_IMAGE:-mysql:8.0.32}
usage() {
cat <<USAGE
Usage: $0 <path-to-backup.sql[.gz|.xz]>
Recreates the local staging MySQL container (${CONTAINER_NAME}) and imports the given SQL dump.
Environment overrides: STAGING_MYSQL_CONTAINER, STAGING_MYSQL_VOLUME, STAGING_MYSQL_PORT,
STAGING_MYSQL_DB, STAGING_MYSQL_ROOT_PASSWORD, STAGING_MYSQL_IMAGE.
USAGE
exit 1
}
if [[ $# -ne 1 ]]; then
usage
fi
backup_path=$1
if [[ ! -f "$backup_path" ]]; then
echo "[ERROR] Backup file not found: $backup_path" >&2
exit 1
fi
if ! command -v docker >/dev/null 2>&1; then
echo "[ERROR] docker command not found" >&2
exit 1
fi
port_in_use() {
if command -v lsof >/dev/null 2>&1; then
lsof -iTCP:"$HOST_PORT" -sTCP:LISTEN -Pn >/dev/null 2>&1 && return 0
fi
if command -v netstat >/dev/null 2>&1; then
netstat -an | grep -E "\.$HOST_PORT .*LISTEN" >/dev/null 2>&1 && return 0
fi
return 1
}
confirm_remove() {
local prompt="$1"
read -r -p "$prompt (yes/no): " reply
if [[ "$reply" != "yes" ]]; then
echo "操作已取消"
exit 1
fi
}
existing_container_id=$(docker ps -a --filter "name=^${CONTAINER_NAME}$" -q)
if [[ -n "$existing_container_id" ]]; then
echo "[WARN] Container ${CONTAINER_NAME} already exists."
confirm_remove "Remove the existing staging container and recreate it?"
docker rm -f "$CONTAINER_NAME" >/dev/null
fi
echo "[INFO] Removing existing volume (if any): ${VOLUME_NAME}"
if docker volume ls -q --filter "name=^${VOLUME_NAME}$" | grep -Fxq "$VOLUME_NAME"; then
docker volume rm "$VOLUME_NAME" >/dev/null
fi
if port_in_use; then
containers_on_port=$(docker ps --filter "publish=${HOST_PORT}" --format '{{.ID}}\t{{.Names}}')
if [[ -n "$containers_on_port" ]]; then
echo "[WARN] Host port ${HOST_PORT} is currently used by the following container(s):"
while IFS=$'\t' read -r cid cname; do
[[ -z "$cid" ]] && continue
echo " - ${cname} (${cid})"
done <<<"$containers_on_port"
confirm_remove "Stop and remove these container(s)?"
while IFS=$'\t' read -r cid cname; do
[[ -z "$cid" ]] && continue
docker stop "$cid" >/dev/null
docker rm "$cid" >/dev/null
done <<<"$containers_on_port"
else
echo "[ERROR] Host port ${HOST_PORT} is already in use. Set STAGING_MYSQL_PORT to an unused port or stop the process using it." >&2
exit 1
fi
if port_in_use; then
echo "[ERROR] Host port ${HOST_PORT} is still in use after stopping containers. Please free the port manually or change STAGING_MYSQL_PORT." >&2
exit 1
fi
fi
echo "[INFO] Starting fresh MySQL container ${CONTAINER_NAME}"
docker run -d \
--name "$CONTAINER_NAME" \
-p "${HOST_PORT}:3306" \
-e MYSQL_ROOT_PASSWORD="$ROOT_PASSWORD" \
-e MYSQL_DATABASE="$DB_NAME" \
-v "$VOLUME_NAME:/var/lib/mysql" \
"$MYSQL_IMAGE" \
--lower_case_table_names=1 \
--explicit_defaults_for_timestamp=1 \
--character-set-server=utf8mb4 \
--collation-server=utf8mb4_unicode_ci >/dev/null
echo -n "[INFO] Waiting for MySQL to accept connections"
for attempt in {1..60}; do
if docker exec "$CONTAINER_NAME" mysqladmin ping -h127.0.0.1 -uroot -p"$ROOT_PASSWORD" --silent >/dev/null 2>&1; then
echo " - ready"
break
fi
sleep 2
echo -n "."
if [[ $attempt -eq 60 ]]; then
echo "\n[ERROR] MySQL did not become ready in time" >&2
exit 1
fi
done
case "$backup_path" in
*.gz)
reader=(gzip -dc -- "$backup_path")
;;
*.xz)
reader=(xz -dc -- "$backup_path")
;;
*.zip)
reader=(unzip -p -- "$backup_path")
;;
*)
reader=(cat -- "$backup_path")
;;
esac
echo "[INFO] Importing backup $backup_path into ${DB_NAME}"
"${reader[@]}" | docker exec -i "$CONTAINER_NAME" mysql -uroot -p"$ROOT_PASSWORD" --default-character-set=utf8mb4 "$DB_NAME"
echo "[INFO] Staging database restored. Container ${CONTAINER_NAME} is listening on port ${HOST_PORT}."