using Fake.Application; using Fake.Rbac.Infrastructure; using Microsoft.EntityFrameworkCore; namespace Oms.Application.Shared; /// /// 微信/客户注册前校验 RBAC 登录账号,避免 user 表已存在但 t_customer 未创建时 EF 抛出英文 DbUpdateException。 /// public static class OmsRbacRegistrationGuard { public static async Task EnsureLoginIdentityAvailableAsync( FakeRbacDbContext rbacDbContext, string account, string? email, CancellationToken cancellationToken = default) { var normalizedAccount = account.Trim(); var normalizedEmail = string.IsNullOrWhiteSpace(email) ? null : email.Trim(); if (await ExistsActiveUserByAccountAsync(rbacDbContext, normalizedAccount, cancellationToken)) { throw new BusinessException("登录账号已存在,请直接绑定或使用其他手机号"); } if (normalizedEmail != null && await ExistsActiveUserByEmailAsync(rbacDbContext, normalizedEmail, cancellationToken)) { throw new BusinessException("邮箱已被占用,请更换邮箱或直接绑定已有账号"); } } public static BusinessException ToSaveBusinessException(Exception ex) { var message = ExtractDeepMessage(ex); if (ContainsDuplicateKey(message)) { if (message.Contains("Account", StringComparison.OrdinalIgnoreCase) || message.Contains("uk_user_account", StringComparison.OrdinalIgnoreCase) || message.Contains("IX_user_Account", StringComparison.OrdinalIgnoreCase)) { return new BusinessException("登录账号已存在,请直接绑定或使用其他手机号"); } if (message.Contains("Email", StringComparison.OrdinalIgnoreCase) || message.Contains("uk_user_email", StringComparison.OrdinalIgnoreCase)) { return new BusinessException("邮箱已被占用,请更换邮箱或直接绑定已有账号"); } if (message.Contains("Phone", StringComparison.OrdinalIgnoreCase) || message.Contains("uk_cus_phone", StringComparison.OrdinalIgnoreCase)) { return new BusinessException("手机号已存在"); } if (message.Contains("uk_cus_email", StringComparison.OrdinalIgnoreCase)) { return new BusinessException("邮箱已存在"); } if (message.Contains("WechatOpenid", StringComparison.OrdinalIgnoreCase) || message.Contains("uk_cus_openid", StringComparison.OrdinalIgnoreCase) || message.Contains("uk_customer_wechat_openid", StringComparison.OrdinalIgnoreCase)) { return new BusinessException("当前微信已绑定账号"); } return new BusinessException("账号信息已存在,请直接绑定或更换手机号/邮箱"); } if (message.Contains("Data too long", StringComparison.OrdinalIgnoreCase) || message.Contains("1406", StringComparison.OrdinalIgnoreCase)) { if (message.Contains("Avatar", StringComparison.OrdinalIgnoreCase)) { return new BusinessException("微信头像地址过长,请稍后重试或联系客服"); } if (message.Contains("Nickname", StringComparison.OrdinalIgnoreCase) || message.Contains("CustomerName", StringComparison.OrdinalIgnoreCase) || message.Contains("Name", StringComparison.OrdinalIgnoreCase)) { return new BusinessException("客户名称或微信昵称过长,请修改后重试"); } return new BusinessException("注册信息字段过长,请缩短客户名称后重试"); } if (message.Contains("foreign key constraint fails", StringComparison.OrdinalIgnoreCase) || message.Contains("1452", StringComparison.OrdinalIgnoreCase)) { return new BusinessException("客户角色未正确初始化,请联系管理员"); } if (message.Contains("Cannot insert duplicate key", StringComparison.OrdinalIgnoreCase) || message.Contains("2627", StringComparison.OrdinalIgnoreCase)) { return new BusinessException("账号信息已存在,请直接绑定或更换手机号/邮箱"); } var reason = ExtractActionableReason(ex); return new BusinessException($"注册失败:{reason}"); } private static string ExtractActionableReason(Exception ex) { var parts = new List(); for (var current = ex; current != null; current = current.InnerException) { if (string.IsNullOrWhiteSpace(current.Message)) { continue; } if (current.Message.Contains("saving the entity changes", StringComparison.OrdinalIgnoreCase)) { continue; } parts.Add(current.Message.Trim()); } var reason = parts.LastOrDefault(); if (string.IsNullOrWhiteSpace(reason)) { return "请稍后重试或联系客服"; } return reason.Length <= 160 ? reason : reason[..160]; } private static async Task ExistsActiveUserByAccountAsync( FakeRbacDbContext rbacDbContext, string account, CancellationToken cancellationToken) { // 唯一索引不按 IsDeleted 区分,软删除账号仍会触发 Duplicate entry。 var hit = await rbacDbContext.Database .SqlQuery( $"SELECT 1 AS Value FROM `user` WHERE `Account` = {account} LIMIT 1") .FirstOrDefaultAsync(cancellationToken); return hit.HasValue; } private static async Task ExistsActiveUserByEmailAsync( FakeRbacDbContext rbacDbContext, string email, CancellationToken cancellationToken) { var hit = await rbacDbContext.Database .SqlQuery( $"SELECT 1 AS Value FROM `user` WHERE `Email` = {email} LIMIT 1") .FirstOrDefaultAsync(cancellationToken); return hit.HasValue; } private static string ExtractDeepMessage(Exception ex) { var parts = new List(); for (var current = ex; current != null; current = current.InnerException) { if (!string.IsNullOrWhiteSpace(current.Message)) { parts.Add(current.Message); } } return string.Join(" | ", parts); } private static bool ContainsDuplicateKey(string message) => message.Contains("Duplicate entry", StringComparison.OrdinalIgnoreCase) || message.Contains("1062", StringComparison.OrdinalIgnoreCase) || message.Contains("unique constraint", StringComparison.OrdinalIgnoreCase) || message.Contains("UNIQUE constraint failed", StringComparison.OrdinalIgnoreCase); }