From bf81f00c9113c3c5e21a9ce3512f5c955127e9be Mon Sep 17 00:00:00 2001 From: zzh <1368735060@qq.com> Date: Thu, 2 Jul 2026 15:08:24 +0800 Subject: [PATCH] =?UTF-8?q?fix(wechat):=20=E6=B3=A8=E5=86=8C=E5=A4=B1?= =?UTF-8?q?=E8=B4=A5=E6=97=B6=E8=BF=94=E5=9B=9E=E4=B8=AD=E6=96=87=E9=94=99?= =?UTF-8?q?=E8=AF=AF=E5=B9=B6=E6=A0=A1=E9=AA=8C=20RBAC=20=E8=B4=A6?= =?UTF-8?q?=E5=8F=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 避免 user 表重复写入抛出英文 DbUpdateException;注册前检查 RBAC 登录账号/邮箱是否已占用。 Co-authored-by: Cursor --- .../Shared/OmsRbacRegistrationGuard.cs | 118 ++++++++++++++++++ .../WeChat/WeChatCustomerService.cs | 5 +- 2 files changed, 121 insertions(+), 2 deletions(-) create mode 100644 src/Oms.Application/Shared/OmsRbacRegistrationGuard.cs 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); } }