From 3efa40ce086bb4bc85aaaeb7122980a9005c164a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=A9=AC=E6=A2=93=E5=BD=AD?= Date: Wed, 17 Jun 2026 15:42:32 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E8=BF=90=E5=8A=A8=E5=91=98?= =?UTF-8?q?=E5=8F=B7=E7=A0=81=E5=8E=BB=E9=87=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../AppUser/AppUserService.cs | 56 ++++++++++++++++++- .../AppUser/Dtos/AppUserDtos.cs | 10 ++++ YD_Event.Application/Client/Dtos/MyDtos.cs | 11 ++++ YD_Event.Application/Client/MyController.cs | 22 ++++++++ YD_Event.Application/YD_Event.Application.xml | 27 +++++++++ YD_Event.Core/Entities/AppUser.cs | 6 ++ YD_Event.Core/YD_Event.Core.xml | 5 ++ 7 files changed, 136 insertions(+), 1 deletion(-) diff --git a/YD_Event.Application/AppUser/AppUserService.cs b/YD_Event.Application/AppUser/AppUserService.cs index a18b962..b207deb 100644 --- a/YD_Event.Application/AppUser/AppUserService.cs +++ b/YD_Event.Application/AppUser/AppUserService.cs @@ -180,6 +180,39 @@ public class AppUserService : BaseService, ITransient // 手机号为空时,跳过基于手机号的重复检测(单位添加运动员时,联系电话非必填) var mobileIsEmpty = string.IsNullOrWhiteSpace(input.Mobile); + // ============== 新增:证件号查重验证 ============== + // 身份证/户口本认证时检查身份证号是否重复 + if ((input.AuthType == 1 || input.AuthType == 3) && !string.IsNullOrWhiteSpace(input.IdCardNumber)) + { + var duplicateIdCard = await _appUserRep.AsQueryable() + .Where(x => x.IdCardNumber == input.IdCardNumber + && x.PersonType == input.PersonType + && !x.DeleteMark + && (!input.Id.HasValue || x.Id != input.Id.Value)) + .FirstAsync(); + + if (duplicateIdCard != null) + { + throw Oops.Bah($"系统中已存在相同身份证号的{GetPersonTypeDisplay(input.PersonType)},请核对信息"); + } + } + + // 护照认证时检查护照号是否重复 + if (input.AuthType == 2 && !string.IsNullOrWhiteSpace(input.PassportNumber)) + { + var duplicatePassport = await _appUserRep.AsQueryable() + .Where(x => x.PassportNumber == input.PassportNumber + && x.PersonType == input.PersonType + && !x.DeleteMark + && (!input.Id.HasValue || x.Id != input.Id.Value)) + .FirstAsync(); + + if (duplicatePassport != null) + { + throw Oops.Bah($"系统中已存在相同护照号的{GetPersonTypeDisplay(input.PersonType)},请核对信息"); + } + } + // 先检查是否存在相同手机号且相同类型的运动员在其他单位 AppUserEntity? existingInOtherUnit = null; if (!mobileIsEmpty) @@ -319,21 +352,24 @@ public class AppUserService : BaseService, ITransient entity.BirthDate = input.BirthDate; entity.Age = input.Age; - // 根据认证类型把图片URL写入对应字段,并清空其他类型图片 + // 根据认证类型把图片URL和证件号写入对应字段,并清空其他类型图片 if (input.AuthType == 1) { entity.AuthType = 1; entity.IdCardImageUrl = input.IdCardImageUrl; entity.IdCardBackImageUrl = input.IdCardBackImageUrl; entity.PassportPhotoUrl = null; + entity.PassportNumber = null; entity.HouseholdPhotoUrl = null; } else if (input.AuthType == 2) { entity.AuthType = 2; entity.PassportPhotoUrl = input.PassportPhotoUrl ?? input.IdCardImageUrl; + entity.PassportNumber = input.PassportNumber; entity.IdCardImageUrl = null; entity.IdCardBackImageUrl = null; + entity.IdCardNumber = null; entity.HouseholdPhotoUrl = null; } else if (input.AuthType == 3) @@ -343,6 +379,7 @@ public class AppUserService : BaseService, ITransient entity.IdCardImageUrl = null; entity.IdCardBackImageUrl = null; entity.PassportPhotoUrl = null; + entity.PassportNumber = null; } else { @@ -352,6 +389,7 @@ public class AppUserService : BaseService, ITransient entity.IdCardImageUrl = null; entity.IdCardBackImageUrl = null; entity.PassportPhotoUrl = null; + entity.PassportNumber = null; entity.HouseholdPhotoUrl = null; } @@ -411,6 +449,22 @@ public class AppUserService : BaseService, ITransient } } + /// + /// 获取人员类型显示名称 + /// + /// + /// + private static string GetPersonTypeDisplay(PersonType personType) + { + return personType switch + { + PersonType.Athlete => "运动员", + PersonType.Coach => "教练", + PersonType.Leader => "领队", + _ => "成员" + }; + } + #endregion #region 领队/教练管理 (向下兼容) diff --git a/YD_Event.Application/AppUser/Dtos/AppUserDtos.cs b/YD_Event.Application/AppUser/Dtos/AppUserDtos.cs index 9d56783..3aa838e 100644 --- a/YD_Event.Application/AppUser/Dtos/AppUserDtos.cs +++ b/YD_Event.Application/AppUser/Dtos/AppUserDtos.cs @@ -172,6 +172,11 @@ public class CreateOrUpdateMemberInput //[RegularExpression(@"(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)", ErrorMessage = "身份证号格式不正确")] public string IdCardNumber { get; set; } + /// + /// 护照号码(护照认证时使用) + /// + public string? PassportNumber { get; set; } + /// /// 身份证图片URL /// @@ -282,6 +287,11 @@ public class AppUserOutput /// public string IdCardNumber { get; set; } + /// + /// 护照号码 + /// + public string? PassportNumber { get; set; } + /// /// 身份证图片URL /// diff --git a/YD_Event.Application/Client/Dtos/MyDtos.cs b/YD_Event.Application/Client/Dtos/MyDtos.cs index 764c11c..4d881f6 100644 --- a/YD_Event.Application/Client/Dtos/MyDtos.cs +++ b/YD_Event.Application/Client/Dtos/MyDtos.cs @@ -97,6 +97,11 @@ public class MyAuthInfoDto /// public string? IdCardNumber { get; set; } + /// + /// 护照号码(认证类型2时使用) + /// + public string? PassportNumber { get; set; } + /// /// 身份证正面图片(认证类型1时使用) /// @@ -175,6 +180,12 @@ public class SubmitAuthInput [MaxLength(20, ErrorMessage = "身份证号不能超过20个字符")] public string? IdCardNumber { get; set; } + /// + /// 护照号码(认证类型2时必填) + /// + [MaxLength(20, ErrorMessage = "护照号码不能超过20个字符")] + public string? PassportNumber { get; set; } + /// /// 身份证正面图片(认证类型1时必填) /// diff --git a/YD_Event.Application/Client/MyController.cs b/YD_Event.Application/Client/MyController.cs index 5503e62..053b000 100644 --- a/YD_Event.Application/Client/MyController.cs +++ b/YD_Event.Application/Client/MyController.cs @@ -145,6 +145,7 @@ public class MyController : IDynamicApiController IdCardNumber = user.IdCardNumber, IdCardImageUrl = user.IdCardImageUrl, IdCardBackImageUrl = user.IdCardBackImageUrl, + PassportNumber = user.PassportNumber, PassportPhotoUrl = user.PassportPhotoUrl, HouseholdPhotoUrl = user.HouseholdPhotoUrl, AuditStatus = user.AuditStatus, @@ -172,17 +173,33 @@ public class MyController : IDynamicApiController if (string.IsNullOrEmpty(input.IdCardNumber)) throw Oops.Bah("身份证号不能为空"); if (string.IsNullOrEmpty(input.IdCardImageUrl)) throw Oops.Bah("身份证正面图片不能为空"); if (string.IsNullOrEmpty(input.IdCardBackImageUrl)) throw Oops.Bah("身份证背面图片不能为空"); + + // 身份证号查重 + var duplicateIdCard = await _appUserRepository.GetFirstAsync(u => + u.IdCardNumber == input.IdCardNumber && u.Id != userId && !u.DeleteMark); + if (duplicateIdCard != null) throw Oops.Bah("该身份证号已被其他用户使用"); } else if (input.AuthType == 2) { if (string.IsNullOrEmpty(input.PassportPhotoUrl)) throw Oops.Bah("护照图片不能为空"); + if (string.IsNullOrEmpty(input.PassportNumber)) throw Oops.Bah("护照号码不能为空"); if (!input.Gender.HasValue) throw Oops.Bah("性别不能为空"); if (!input.BirthDate.HasValue) throw Oops.Bah("出生日期不能为空"); + + // 护照号查重 + var duplicatePassport = await _appUserRepository.GetFirstAsync(u => + u.PassportNumber == input.PassportNumber && u.Id != userId && !u.DeleteMark); + if (duplicatePassport != null) throw Oops.Bah("该护照号码已被其他用户使用"); } else if (input.AuthType == 3) { if (string.IsNullOrEmpty(input.HouseholdPhotoUrl)) throw Oops.Bah("户口本图片不能为空"); if (string.IsNullOrEmpty(input.IdCardNumber)) throw Oops.Bah("身份证号不能为空"); + + // 身份证号查重 + var duplicateIdCard = await _appUserRepository.GetFirstAsync(u => + u.IdCardNumber == input.IdCardNumber && u.Id != userId && !u.DeleteMark); + if (duplicateIdCard != null) throw Oops.Bah("该身份证号已被其他用户使用"); } else { @@ -226,15 +243,18 @@ public class MyController : IDynamicApiController user.IdCardImageUrl = input.IdCardImageUrl; user.IdCardBackImageUrl = input.IdCardBackImageUrl; user.PassportPhotoUrl = null; + user.PassportNumber = null; user.HouseholdPhotoUrl = null; user.Gender = (Gender)input.Gender.Value; user.BirthDate = input.BirthDate.Value; } else if (input.AuthType == 2) { + user.PassportNumber = input.PassportNumber; user.PassportPhotoUrl = input.PassportPhotoUrl; user.IdCardImageUrl = null; user.IdCardBackImageUrl = null; + user.IdCardNumber = null; user.HouseholdPhotoUrl = null; user.Gender = (Gender)input.Gender.Value; user.BirthDate = input.BirthDate.Value; @@ -247,6 +267,7 @@ public class MyController : IDynamicApiController user.IdCardImageUrl = null; user.IdCardBackImageUrl = null; user.PassportPhotoUrl = null; + user.PassportNumber = null; if (input.Gender.HasValue) { user.Gender = (Gender)input.Gender.Value; @@ -264,6 +285,7 @@ public class MyController : IDynamicApiController user.IdCardImageUrl = null; user.IdCardBackImageUrl = null; user.PassportPhotoUrl = null; + user.PassportNumber = null; user.HouseholdPhotoUrl = null; } diff --git a/YD_Event.Application/YD_Event.Application.xml b/YD_Event.Application/YD_Event.Application.xml index 90c9c19..37313ea 100644 --- a/YD_Event.Application/YD_Event.Application.xml +++ b/YD_Event.Application/YD_Event.Application.xml @@ -95,6 +95,13 @@ 新单位ID + + + 获取人员类型显示名称 + + + + 删除成员(解除与单位的关联,而非删除用户) @@ -267,6 +274,11 @@ 身份证号 + + + 护照号码(护照认证时使用) + + 身份证图片URL @@ -372,6 +384,11 @@ 身份证号 + + + 护照号码 + + 身份证图片URL @@ -3067,6 +3084,11 @@ 身份证号(认证类型1时使用) + + + 护照号码(认证类型2时使用) + + 身份证正面图片(认证类型1时使用) @@ -3137,6 +3159,11 @@ 身份证号(认证类型1时必填) + + + 护照号码(认证类型2时必填) + + 身份证正面图片(认证类型1时必填) diff --git a/YD_Event.Core/Entities/AppUser.cs b/YD_Event.Core/Entities/AppUser.cs index c9beaf6..3ac0abe 100644 --- a/YD_Event.Core/Entities/AppUser.cs +++ b/YD_Event.Core/Entities/AppUser.cs @@ -84,6 +84,12 @@ public class AppUser : Entity, ISoftDelete, ICreatedUserId, ICreatedTime, [SugarColumn(Length = 20, IsNullable = true)] public string? IdCardNumber { get; set; } + /// + /// 护照号码 + /// + [SugarColumn(Length = 20, IsNullable = true)] + public string? PassportNumber { get; set; } + /// /// 身份证正面图片URL /// diff --git a/YD_Event.Core/YD_Event.Core.xml b/YD_Event.Core/YD_Event.Core.xml index 393dc29..321470f 100644 --- a/YD_Event.Core/YD_Event.Core.xml +++ b/YD_Event.Core/YD_Event.Core.xml @@ -632,6 +632,11 @@ 身份证号码 + + + 护照号码 + + 身份证正面图片URL