Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
277 changes: 277 additions & 0 deletions config/migration/member/legacy-import.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,277 @@
-- 会员域旧库导入脚本
-- 用途:
-- 1. 从旧库 staging 数据库导入会员与 OAuth2 绑定数据
-- 2. 只负责搬运旧值,不做状态翻转
-- 3. 状态语义修正请继续执行 legacy-status-remap.sql
--
-- 运行前提:
-- 1. 当前连接数据库应为新架构目标库
-- 2. 通过 psql 变量 source_conn 传入 dblink 连接串
-- 3. 目标库已完成 Flyway 初始化

BEGIN;

CREATE EXTENSION IF NOT EXISTS dblink;

WITH legacy_member AS (
SELECT *
FROM dblink(:'source_conn', $db$
SELECT id,
username,
email,
phone,
password_hash,
name,
nickname,
avatar_url,
gender,
birth_date,
status,
member_level,
points,
balance,
register_source,
referrer_id,
last_login_time,
email_verified_time,
phone_verified_time,
profile,
preferences,
created_time,
updated_time,
del_flag
FROM mortise.mortise_member
$db$) AS t(
id BIGINT,
username VARCHAR(50),
email VARCHAR(100),
phone VARCHAR(20),
password_hash VARCHAR(255),
name VARCHAR(100),
nickname VARCHAR(50),
avatar_url VARCHAR(500),
gender VARCHAR(10),
birth_date DATE,
status INTEGER,
member_level VARCHAR(20),
points INTEGER,
balance NUMERIC(10, 2),
register_source VARCHAR(50),
referrer_id BIGINT,
last_login_time TIMESTAMP,
email_verified_time TIMESTAMP,
phone_verified_time TIMESTAMP,
profile JSONB,
preferences JSONB,
created_time TIMESTAMP,
updated_time TIMESTAMP,
del_flag INTEGER
)
)
INSERT INTO mortise.mortise_member (
id,
username,
email,
phone,
password_hash,
name,
nickname,
avatar_url,
gender,
birth_date,
status,
member_level,
points,
balance,
register_source,
referrer_id,
last_login_time,
email_verified_time,
phone_verified_time,
profile,
preferences,
created_time,
updated_time,
del_flag,
current_family_id
)
SELECT id,
username,
email,
phone,
password_hash,
name,
nickname,
avatar_url,
gender,
birth_date,
COALESCE(status, 0),
COALESCE(member_level, 'normal'),
COALESCE(points, 0),
COALESCE(balance, 0.00),
register_source,
referrer_id,
last_login_time,
email_verified_time,
phone_verified_time,
profile,
preferences,
COALESCE(created_time, CURRENT_TIMESTAMP),
COALESCE(updated_time, COALESCE(created_time, CURRENT_TIMESTAMP)),
COALESCE(del_flag, 0),
NULL::BIGINT
FROM legacy_member
ON CONFLICT (id) DO UPDATE
SET username = EXCLUDED.username,
email = EXCLUDED.email,
phone = EXCLUDED.phone,
password_hash = EXCLUDED.password_hash,
name = EXCLUDED.name,
nickname = EXCLUDED.nickname,
avatar_url = EXCLUDED.avatar_url,
gender = EXCLUDED.gender,
birth_date = EXCLUDED.birth_date,
status = EXCLUDED.status,
member_level = EXCLUDED.member_level,
points = EXCLUDED.points,
balance = EXCLUDED.balance,
register_source = EXCLUDED.register_source,
referrer_id = EXCLUDED.referrer_id,
last_login_time = EXCLUDED.last_login_time,
email_verified_time = EXCLUDED.email_verified_time,
phone_verified_time = EXCLUDED.phone_verified_time,
profile = EXCLUDED.profile,
preferences = EXCLUDED.preferences,
created_time = EXCLUDED.created_time,
updated_time = EXCLUDED.updated_time,
del_flag = EXCLUDED.del_flag;

WITH legacy_binding AS (
SELECT *
FROM dblink(:'source_conn', $db$
SELECT id,
member_id,
provider,
open_id,
union_id,
nickname,
avatar,
email,
phone,
gender,
country,
province,
city,
access_token,
refresh_token,
expires_at,
raw_data,
status,
del_flag,
created_time,
updated_time
FROM mortise.mortise_member_oauth2_binding
$db$) AS t(
id BIGINT,
member_id BIGINT,
provider VARCHAR(50),
open_id VARCHAR(255),
union_id VARCHAR(255),
nickname VARCHAR(100),
avatar VARCHAR(500),
email VARCHAR(100),
phone VARCHAR(20),
gender VARCHAR(10),
country VARCHAR(50),
province VARCHAR(50),
city VARCHAR(50),
access_token TEXT,
refresh_token TEXT,
expires_at TIMESTAMP,
raw_data TEXT,
status INTEGER,
del_flag INTEGER,
created_time TIMESTAMP,
updated_time TIMESTAMP
)
)
INSERT INTO mortise.mortise_member_oauth2_binding (
id,
member_id,
provider,
open_id,
union_id,
nickname,
avatar,
email,
phone,
gender,
country,
province,
city,
access_token,
refresh_token,
expires_at,
raw_data,
status,
del_flag,
created_time,
updated_time
)
SELECT b.id,
b.member_id,
b.provider,
b.open_id,
b.union_id,
b.nickname,
b.avatar,
b.email,
b.phone,
b.gender,
b.country,
b.province,
b.city,
b.access_token,
b.refresh_token,
b.expires_at,
b.raw_data,
COALESCE(b.status, 0),
COALESCE(b.del_flag, 0),
COALESCE(b.created_time, CURRENT_TIMESTAMP),
COALESCE(b.updated_time, COALESCE(b.created_time, CURRENT_TIMESTAMP))
FROM legacy_binding b
WHERE EXISTS (
SELECT 1
FROM mortise.mortise_member m
WHERE m.id = b.member_id
)
ON CONFLICT (id) DO UPDATE
SET member_id = EXCLUDED.member_id,
provider = EXCLUDED.provider,
open_id = EXCLUDED.open_id,
union_id = EXCLUDED.union_id,
nickname = EXCLUDED.nickname,
avatar = EXCLUDED.avatar,
email = EXCLUDED.email,
phone = EXCLUDED.phone,
gender = EXCLUDED.gender,
country = EXCLUDED.country,
province = EXCLUDED.province,
city = EXCLUDED.city,
access_token = EXCLUDED.access_token,
refresh_token = EXCLUDED.refresh_token,
expires_at = EXCLUDED.expires_at,
raw_data = EXCLUDED.raw_data,
status = EXCLUDED.status,
del_flag = EXCLUDED.del_flag,
created_time = EXCLUDED.created_time,
updated_time = EXCLUDED.updated_time;

COMMIT;

SELECT 'mortise_member' AS table_name, count(*) AS row_count
FROM mortise.mortise_member
UNION ALL
SELECT 'mortise_member_oauth2_binding' AS table_name, count(*) AS row_count
FROM mortise.mortise_member_oauth2_binding
ORDER BY table_name;
75 changes: 75 additions & 0 deletions config/migration/member/legacy-post-import-validation.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
-- 会员域旧库导入后校验 SQL
-- 用途:
-- 1. 对比会员与 OAuth2 绑定导入后的行数
-- 2. 检查 status 翻转后是否仍存在异常值或关联脏数据
-- 3. 为认证链路回归提供基础验收数据

SELECT CURRENT_TIMESTAMP AS validation_time;

-- 1. 行数概览
SELECT 'row_count' AS check_type,
'mortise_member' AS target_table,
COUNT(*) AS total_rows,
COUNT(*) FILTER (WHERE del_flag = 0) AS active_rows
FROM mortise.mortise_member
UNION ALL
SELECT 'row_count', 'mortise_member_oauth2_binding', COUNT(*), COUNT(*) FILTER (WHERE del_flag = 0)
FROM mortise.mortise_member_oauth2_binding
ORDER BY target_table;

-- 2. status 分布
SELECT 'status_distribution' AS check_type,
'mortise_member' AS target_table,
status::TEXT AS status_value,
COUNT(*) AS row_count
FROM mortise.mortise_member
GROUP BY status
UNION ALL
SELECT 'status_distribution', 'mortise_member_oauth2_binding', status::TEXT, COUNT(*)
FROM mortise.mortise_member_oauth2_binding
GROUP BY status
ORDER BY target_table, status_value;

-- 3. 结构与语义校验
SELECT 'member_invalid_status' AS check_name,
COUNT(*) AS issue_count
FROM mortise.mortise_member
WHERE status NOT IN (0, 1)
OR status IS NULL;

SELECT 'member_oauth2_binding_invalid_status' AS check_name,
COUNT(*) AS issue_count
FROM mortise.mortise_member_oauth2_binding
WHERE status NOT IN (0, 1)
OR status IS NULL;

SELECT 'oauth2_binding_orphan_member' AS check_name,
COUNT(*) AS issue_count
FROM mortise.mortise_member_oauth2_binding binding
LEFT JOIN mortise.mortise_member member ON member.id = binding.member_id
WHERE member.id IS NULL;

SELECT 'enabled_binding_linked_disabled_member' AS check_name,
COUNT(*) AS issue_count
FROM mortise.mortise_member_oauth2_binding binding
JOIN mortise.mortise_member member ON member.id = binding.member_id
WHERE binding.status = 1
AND member.status = 0;

SELECT 'duplicate_provider_open_id' AS check_name,
COUNT(*) AS issue_count
FROM (
SELECT provider, open_id
FROM mortise.mortise_member_oauth2_binding
WHERE del_flag = 0
GROUP BY provider, open_id
HAVING COUNT(*) > 1
) duplicates;

SELECT 'member_missing_login_identifier' AS check_name,
COUNT(*) AS review_count
FROM mortise.mortise_member
WHERE del_flag = 0
AND NULLIF(BTRIM(COALESCE(username, '')), '') IS NULL
AND NULLIF(BTRIM(COALESCE(email, '')), '') IS NULL
AND NULLIF(BTRIM(COALESCE(phone, '')), '') IS NULL;
33 changes: 33 additions & 0 deletions config/migration/member/legacy-status-remap.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
-- 会员域旧库导入后状态修复脚本
-- 用途:
-- 1. 把 old-codes 旧语义 0=正常,1=禁用 的会员状态翻转为新语义 0=禁用,1=启用
-- 2. 同步修正 OAuth2 绑定状态,避免导入后账号可用性失真
--
-- 注意:
-- 1. 该脚本不是 Flyway,不接入应用启动流程
-- 2. 该脚本按“旧值尚未翻转”假设编写,重复执行会再次翻转 0/1
-- 3. 执行前请先备份测试环境数据库

BEGIN;

-- 会员状态:旧语义 0=正常,1=禁用 -> 新语义 0=禁用,1=启用
UPDATE mortise.mortise_member
SET status = CASE status
WHEN 0 THEN 1
WHEN 1 THEN 0
ELSE status
END,
updated_time = CURRENT_TIMESTAMP
WHERE status IN (0, 1);

-- OAuth2 绑定状态:旧语义 0=正常,1=禁用 -> 新语义 0=禁用,1=启用
UPDATE mortise.mortise_member_oauth2_binding
SET status = CASE status
WHEN 0 THEN 1
WHEN 1 THEN 0
ELSE status
END,
updated_time = CURRENT_TIMESTAMP
WHERE status IN (0, 1);

COMMIT;
Loading