diff --git a/src/Oms.Application/Shared/OmsRbacRegistrationGuard.cs b/src/Oms.Application/Shared/OmsRbacRegistrationGuard.cs index 6c00024..258c710 100644 --- a/src/Oms.Application/Shared/OmsRbacRegistrationGuard.cs +++ b/src/Oms.Application/Shared/OmsRbacRegistrationGuard.cs @@ -69,7 +69,65 @@ public static class OmsRbacRegistrationGuard 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( diff --git a/src/Oms.Application/Shared/WeChatRegistrationFieldNormalizer.cs b/src/Oms.Application/Shared/WeChatRegistrationFieldNormalizer.cs new file mode 100644 index 0000000..1d7d9ff --- /dev/null +++ b/src/Oms.Application/Shared/WeChatRegistrationFieldNormalizer.cs @@ -0,0 +1,31 @@ +namespace Oms.Application.Shared; + +/// +/// 微信注册字段裁剪,避免超出库表长度触发保存失败。 +/// +public static class WeChatRegistrationFieldNormalizer +{ + public const int CustomerNameMaxLength = 128; + public const int PhoneMaxLength = 16; + public const int EmailMaxLength = 64; + public const int AvatarMaxLength = 255; + public const int NicknameMaxLength = 64; + public const int OpenidMaxLength = 64; + + public static string TruncateRequired(string value, int maxLength) + { + var normalized = value.Trim(); + return normalized.Length <= maxLength ? normalized : normalized[..maxLength]; + } + + public static string? TruncateOptional(string? value, int maxLength) + { + var normalized = value?.Trim(); + if (string.IsNullOrWhiteSpace(normalized)) + { + return null; + } + + return normalized.Length <= maxLength ? normalized : normalized[..maxLength]; + } +} diff --git a/src/Oms.Domain/CustomersAggregate/Repositories/ICustomerRepository.cs b/src/Oms.Domain/CustomersAggregate/Repositories/ICustomerRepository.cs index d22c4c9..62b07a5 100644 --- a/src/Oms.Domain/CustomersAggregate/Repositories/ICustomerRepository.cs +++ b/src/Oms.Domain/CustomersAggregate/Repositories/ICustomerRepository.cs @@ -15,6 +15,12 @@ public interface ICustomerRepository : IRepository Task ExistsPhoneAsync(string phone, CancellationToken cancellationToken = default); + Task ExistsPhoneAnyAsync(string phone, CancellationToken cancellationToken = default); + + Task ExistsEmailAnyAsync(string email, CancellationToken cancellationToken = default); + + Task ExistsWechatOpenidAnyAsync(string wechatOpenid, CancellationToken cancellationToken = default); + /// /// 查询指定前缀(WyyMM)下已用顺序号的最大值。 /// diff --git a/src/Oms.SqlSugarCore/Customers/CustomerRepository.cs b/src/Oms.SqlSugarCore/Customers/CustomerRepository.cs index 3304742..60fe0aa 100644 --- a/src/Oms.SqlSugarCore/Customers/CustomerRepository.cs +++ b/src/Oms.SqlSugarCore/Customers/CustomerRepository.cs @@ -32,6 +32,33 @@ public class CustomerRepository : OmsSqlSugarRepository, ICustomerRepo return AnyAsync(x => x.Phone == phone, cancellationToken); } + public async Task ExistsPhoneAnyAsync(string phone, CancellationToken cancellationToken = default) + { + var db = await GetDbContextAsync(cancellationToken); + var hit = await db.Ado.SqlQuerySingleAsync( + "SELECT 1 FROM t_customer WHERE Phone = @phone LIMIT 1", + new { phone }); + return hit.HasValue; + } + + public async Task ExistsEmailAnyAsync(string email, CancellationToken cancellationToken = default) + { + var db = await GetDbContextAsync(cancellationToken); + var hit = await db.Ado.SqlQuerySingleAsync( + "SELECT 1 FROM t_customer WHERE Email = @email LIMIT 1", + new { email }); + return hit.HasValue; + } + + public async Task ExistsWechatOpenidAnyAsync(string wechatOpenid, CancellationToken cancellationToken = default) + { + var db = await GetDbContextAsync(cancellationToken); + var hit = await db.Ado.SqlQuerySingleAsync( + "SELECT 1 FROM t_customer WHERE WechatOpenid = @wechatOpenid LIMIT 1", + new { wechatOpenid }); + return hit.HasValue; + } + public async Task GetMaxCustomerCodeSequenceForPrefixAsync( string prefix, CancellationToken cancellationToken = default) diff --git a/src/SimpleAdmin.Api/WeChat/WeChatCustomerService.cs b/src/SimpleAdmin.Api/WeChat/WeChatCustomerService.cs index daabe0b..91c2494 100644 --- a/src/SimpleAdmin.Api/WeChat/WeChatCustomerService.cs +++ b/src/SimpleAdmin.Api/WeChat/WeChatCustomerService.cs @@ -7,6 +7,7 @@ using Fake.Rbac.Infrastructure; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; using Oms.Application.Shared; using Oms.Domain.CustomersAggregate.Entities; using Oms.Domain.CustomersAggregate.Enums; @@ -26,7 +27,8 @@ public class WeChatCustomerService( ICustomerRepository customerRepository, UserService userService, RoleService roleService, - FakeRbacDbContext rbacDbContext) : ApplicationService + FakeRbacDbContext rbacDbContext, + ILogger logger) : ApplicationService { private const string CustomerRoleCode = "Customer"; @@ -80,17 +82,32 @@ public class WeChatCustomerService( { ValidateWechatProfile(input.Wechat); - var customerName = NormalizeRequired(input.CustomerName, "客户名称"); - var phone = NormalizeRequired(input.Phone, "手机号"); - var email = NormalizeRequired(input.Email, "邮箱"); + var customerName = WeChatRegistrationFieldNormalizer.TruncateRequired( + NormalizeRequired(input.CustomerName, "客户名称"), + WeChatRegistrationFieldNormalizer.CustomerNameMaxLength); + var phone = WeChatRegistrationFieldNormalizer.TruncateRequired( + NormalizeRequired(input.Phone, "手机号"), + WeChatRegistrationFieldNormalizer.PhoneMaxLength); + var email = WeChatRegistrationFieldNormalizer.TruncateRequired( + NormalizeRequired(input.Email, "邮箱"), + WeChatRegistrationFieldNormalizer.EmailMaxLength); var password = NormalizeRequired(input.Password, "密码"); + var wechatOpenid = WeChatRegistrationFieldNormalizer.TruncateRequired( + input.Wechat.Openid.Trim(), + WeChatRegistrationFieldNormalizer.OpenidMaxLength); + var wechatNickname = WeChatRegistrationFieldNormalizer.TruncateOptional( + input.Wechat.Nickname, + WeChatRegistrationFieldNormalizer.NicknameMaxLength); + var wechatAvatar = WeChatRegistrationFieldNormalizer.TruncateOptional( + input.Wechat.Avatar, + WeChatRegistrationFieldNormalizer.AvatarMaxLength); if (password.Length < 6) { throw new BusinessException("密码长度不能少于6位"); } - if (await customerRepository.GetByWechatOpenidAsync(input.Wechat.Openid, cancellationToken) != null) + if (await customerRepository.ExistsWechatOpenidAnyAsync(wechatOpenid, cancellationToken)) { throw new BusinessException("当前微信已绑定账号"); } @@ -109,11 +126,10 @@ public class WeChatCustomerService( Account = phone, Password = password, Email = email, - Avatar = NormalizeOptional(input.Wechat.Avatar), - RoleIds = [] + Avatar = wechatAvatar, + RoleIds = [customerRoleId] }); - await userService.AssignRolesAsync(user.Id, [customerRoleId]); var userId = await GetRbacUserMapIdOrThrowAsync(user.Id, cancellationToken); var entity = new Customer { @@ -123,9 +139,9 @@ public class WeChatCustomerService( Phone = phone, Email = email, Password = OmsPasswordHelper.ToMd5(password), - WechatOpenid = input.Wechat.Openid, - WechatNickname = NormalizeOptional(input.Wechat.Nickname), - WechatAvatar = NormalizeOptional(input.Wechat.Avatar), + WechatOpenid = wechatOpenid, + WechatNickname = wechatNickname, + WechatAvatar = wechatAvatar, Status = CustomerStatus.Normal }; await customerRepository.InsertAsync(entity); @@ -137,6 +153,7 @@ public class WeChatCustomerService( await TryRollbackRbacUserAsync(user.Id); } + logger.LogWarning(ex, "微信客户注册失败: phone={Phone}, email={Email}, openid={Openid}", phone, email, wechatOpenid); throw ex is BusinessException ? ex : OmsRbacRegistrationGuard.ToSaveBusinessException(ex); } } @@ -207,12 +224,12 @@ public class WeChatCustomerService( throw new BusinessException("客户编号已存在"); } - if (await customerRepository.GetByPhoneAsync(phone, cancellationToken) != null) + if (await customerRepository.ExistsPhoneAnyAsync(phone, cancellationToken)) { throw new BusinessException("手机号已存在"); } - if (await customerRepository.GetByEmailAsync(email, cancellationToken) != null) + if (await customerRepository.ExistsEmailAnyAsync(email, cancellationToken)) { throw new BusinessException("邮箱已存在"); }