diff --git a/src/Oms.Application/Shared/OmsRbacRegistrationGuard.cs b/src/Oms.Application/Shared/OmsRbacRegistrationGuard.cs
new file mode 100644
index 0000000..f47ce1d
--- /dev/null
+++ b/src/Oms.Application/Shared/OmsRbacRegistrationGuard.cs
@@ -0,0 +1,118 @@
+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("账号信息已存在,请直接绑定或更换手机号/邮箱");
+ }
+
+ return new BusinessException("注册失败,请稍后重试或联系客服");
+ }
+
+ private static async Task ExistsActiveUserByAccountAsync(
+ FakeRbacDbContext rbacDbContext,
+ string account,
+ CancellationToken cancellationToken)
+ {
+ var hit = await rbacDbContext.Database
+ .SqlQuery(
+ $"SELECT 1 AS Value FROM `user` WHERE `IsDeleted` = 0 AND `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 `IsDeleted` = 0 AND `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);
+}
diff --git a/src/SimpleAdmin.Api/WeChat/WeChatCustomerService.cs b/src/SimpleAdmin.Api/WeChat/WeChatCustomerService.cs
index 7eaf2b7..ce02226 100644
--- a/src/SimpleAdmin.Api/WeChat/WeChatCustomerService.cs
+++ b/src/SimpleAdmin.Api/WeChat/WeChatCustomerService.cs
@@ -97,6 +97,7 @@ public class WeChatCustomerService(
var customerCode = await CustomerCodeGenerator.GenerateNextAsync(customerRepository, cancellationToken);
await EnsureCustomerIdentityUniqueAsync(customerCode, phone, email, cancellationToken);
+ await OmsRbacRegistrationGuard.EnsureLoginIdentityAvailableAsync(rbacDbContext, phone, email, cancellationToken);
var customerRoleId = await GetCustomerRoleIdOrThrowAsync(cancellationToken);
var user = await userService.CreateAsync(new UserCreateDto
@@ -128,10 +129,10 @@ public class WeChatCustomerService(
};
await customerRepository.InsertAsync(entity);
}
- catch
+ catch (Exception ex)
{
await TryRollbackRbacUserAsync(user.Id);
- throw;
+ throw ex is BusinessException ? ex : OmsRbacRegistrationGuard.ToSaveBusinessException(ex);
}
}