-- ============================================================================= -- migrate-super-individual-persons-bind.sql -- 超级个体(有效 VIP)与 @人物 persons 对齐 — 数据侧「补绑」迁移 -- -- 说明: -- 1) 新建 persons 行依赖存客宝 API(ckb_plan_id / ckb_api_key),请勿用纯 SQL 硬插完整行。 -- 未绑上的用户:部署 soul-api 后,在管理端「内容管理 → @列表」点刷新,由接口自动 ensurePersonForUser。 -- 2) 本脚本只做安全场景:已有 @人物、user_id 为空,且「未绑定 person 的 VIP」里仅一人昵称与 person.name 一致时,写入 user_id。 -- 3) 执行前请备份库;建议先只跑「第一节 SELECT」看缺口,再跑 UPDATE。 -- 4) VIP 口径:is_vip = 1 且 vip_expire_date > NOW()(与 activeSuperIndividualUserIDs 主路径一致)。 -- ============================================================================= -- --------------------------------------------------------------------------- -- 1) 缺口清单:有效 VIP,且没有任何 person.user_id 指向该用户 -- --------------------------------------------------------------------------- SELECT u.id AS user_id, TRIM(u.nickname) AS nickname, u.vip_expire_date, u.is_vip FROM users u WHERE u.is_vip = 1 AND u.vip_expire_date > NOW() AND NOT EXISTS ( SELECT 1 FROM persons p WHERE p.user_id IS NOT NULL AND TRIM(p.user_id) <> '' AND p.user_id = u.id ) ORDER BY u.vip_sort ASC, u.vip_activated_at DESC, u.id; -- --------------------------------------------------------------------------- -- 2) 冲突检查:多个未绑定的 person 同名(本脚本不会自动处理,需人工合并或走 API) -- --------------------------------------------------------------------------- SELECT TRIM(p.name) AS name_norm, COUNT(*) AS unbound_count FROM persons p WHERE p.user_id IS NULL OR TRIM(p.user_id) = '' GROUP BY TRIM(p.name) HAVING COUNT(*) > 1; -- --------------------------------------------------------------------------- -- 3) 同名唯一补绑:未绑 person ↔ 有效 VIP(昵称一致),且该 VIP 尚无 person -- MySQL 8+ / 5.7 均可用;user_id 列为空串时按未绑定处理 -- --------------------------------------------------------------------------- UPDATE persons p INNER JOIN users u ON TRIM(u.nickname) = TRIM(p.name) AND CHAR_LENGTH(TRIM(COALESCE(u.nickname, ''))) > 0 SET p.user_id = u.id, p.updated_at = NOW() WHERE (p.user_id IS NULL OR TRIM(p.user_id) = '') AND u.is_vip = 1 AND u.vip_expire_date > NOW() AND NOT EXISTS ( SELECT 1 FROM persons px WHERE px.user_id = u.id ) AND ( SELECT COUNT(*) FROM persons p3 WHERE TRIM(p3.name) = TRIM(p.name) AND (p3.user_id IS NULL OR TRIM(p3.user_id) = '') ) = 1; -- --------------------------------------------------------------------------- -- 4) 可选:将本次补绑且仍为「手工」来源的 VIP 行标为 vip_sync(与统一计划展示一致;按业务需要取消注释) -- --------------------------------------------------------------------------- -- UPDATE persons p -- INNER JOIN users u ON p.user_id = u.id -- SET p.person_source = 'vip_sync', p.updated_at = NOW() -- WHERE u.is_vip = 1 -- AND u.vip_expire_date > NOW() -- AND (p.person_source IS NULL OR p.person_source = ''); -- --------------------------------------------------------------------------- -- 5) 执行后再跑第一节 SELECT,应只剩「无同名 person」的 VIP(须由线上 API 创建 person) -- ---------------------------------------------------------------------------