修改bug

This commit is contained in:
马梓彭 2026-04-13 15:28:25 +08:00
parent 5dbcf3ce5e
commit d3c3a847b0
11 changed files with 339 additions and 145 deletions

View File

@ -177,6 +177,11 @@ public class CreateOrUpdateMemberInput
/// </summary>
public string? IdCardImageUrl { get; set; }
/// <summary>
/// 身份证背面图片URL
/// </summary>
public string? IdCardBackImageUrl { get; set; }
/// <summary>
/// 护照图片URL
/// </summary>
@ -273,6 +278,11 @@ public class AppUserOutput
/// </summary>
public string? IdCardImageUrl { get; set; }
/// <summary>
/// 身份证背面图片URL
/// </summary>
public string? IdCardBackImageUrl { get; set; }
/// <summary>
/// 护照图片URL
/// </summary>

View File

@ -102,6 +102,11 @@ public class MyAuthInfoDto
/// </summary>
public string? IdCardImageUrl { get; set; }
/// <summary>
/// 身份证背面图片(认证类型1时使用)
/// </summary>
public string? IdCardBackImageUrl { get; set; }
/// <summary>
/// 护照图片(认证类型2时使用)
/// </summary>
@ -171,6 +176,12 @@ public class SubmitAuthInput
[MaxLength(500, ErrorMessage = "图片URL不能超过500个字符")]
public string? IdCardImageUrl { get; set; }
/// <summary>
/// 身份证背面图片(认证类型1时必填)
/// </summary>
[MaxLength(500, ErrorMessage = "图片URL不能超过500个字符")]
public string? IdCardBackImageUrl { get; set; }
/// <summary>
/// 护照图片(认证类型2时必填)
/// </summary>

View File

@ -87,25 +87,7 @@ public class MyController : IDynamicApiController
var pointsBalance = (int)(pointsIn - pointsOut);
// 认证状态: 0-待提交, 1-已认证, 2-审核中, 3-失败
int authStatus = 0;
bool isAuthInfoSubmitted = !string.IsNullOrEmpty(user.Name) && !string.IsNullOrEmpty(user.IdCardNumber);
if (!isAuthInfoSubmitted)
{
authStatus = 0; // 未认证
}
else if (user.AuditStatus == AuditStatus.Pass)
{
authStatus = 1; // 已认证
}
else if (user.AuditStatus == AuditStatus.PendingAudit)
{
authStatus = 2; // 审核中
}
else
{
authStatus = 3; // 失败/其他
}
int authStatus = (int)user.AuditStatus;
// 获取单位名称
string? merchantName = null;
@ -155,6 +137,7 @@ public class MyController : IDynamicApiController
BirthDate = user.BirthDate,
IdCardNumber = user.IdCardNumber,
IdCardImageUrl = user.IdCardImageUrl,
IdCardBackImageUrl = user.IdCardBackImageUrl,
PassportPhotoUrl = user.PassportPhotoUrl,
AuditStatus = user.AuditStatus,
AuditRemark = user.AuditRemark
@ -180,6 +163,7 @@ 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("身份证背面图片不能为空");
}
else if (input.AuthType == 2)
{
@ -201,12 +185,14 @@ public class MyController : IDynamicApiController
{
user.IdCardNumber = input.IdCardNumber;
user.IdCardImageUrl = input.IdCardImageUrl;
user.IdCardBackImageUrl = input.IdCardBackImageUrl;
user.PassportPhotoUrl = null; // 清空护照图片
}
else
{
user.PassportPhotoUrl = input.PassportPhotoUrl;
user.IdCardImageUrl = null; // 清空身份证图片
user.IdCardBackImageUrl = null; // 清空身份证背面图片
user.Gender = (Gender)input.Gender.Value;
user.BirthDate = input.BirthDate.Value;
}

View File

@ -0,0 +1,98 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
namespace YD_Event.Application.Event;
/// <summary>
/// 支付超时处理后台服务
/// </summary>
public class PaymentExpirationBackgroundService : BackgroundService
{
private readonly IServiceProvider _serviceProvider;
private readonly ILogger<PaymentExpirationBackgroundService> _logger;
private readonly PeriodicTimer _timer;
public PaymentExpirationBackgroundService(
IServiceProvider serviceProvider,
ILogger<PaymentExpirationBackgroundService> logger)
{
_serviceProvider = serviceProvider;
_logger = logger;
_timer = new PeriodicTimer(TimeSpan.FromMinutes(5));
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
_logger.LogInformation("支付超时处理后台服务已启动");
try
{
while (await _timer.WaitForNextTickAsync(stoppingToken))
{
await ProcessExpiredPaymentsAsync(stoppingToken);
}
}
catch (OperationCanceledException)
{
_logger.LogInformation("支付超时处理后台服务已停止");
}
}
private async Task ProcessExpiredPaymentsAsync(CancellationToken stoppingToken)
{
try
{
_logger.LogInformation("开始处理超时未支付订单");
using var scope = _serviceProvider.CreateScope();
var registrationRepository = scope.ServiceProvider.GetRequiredService<ISqlSugarRepository<EventRegistration>>();
var orderRepository = scope.ServiceProvider.GetRequiredService<ISqlSugarRepository<YD_Event.Core.Entities.Order>>();
var now = DateTime.Now;
var expirationTime = now.AddMinutes(-30);
var expiredRegistrations = await registrationRepository.AsQueryable()
.Where(x => x.PaymentMethod == 2
&& x.PaymentStatus == 0
&& x.CreatedTime <= expirationTime
&& !x.DeleteMark)
.ToListAsync(stoppingToken);
if (expiredRegistrations.Any())
{
_logger.LogInformation("发现 {Count} 个超时未支付的线上订单", expiredRegistrations.Count);
var idsToCancel = expiredRegistrations.Select(x => x.Id).ToList();
var orderNos = expiredRegistrations.Where(x => !string.IsNullOrEmpty(x.OrderNo)).Select(x => x.OrderNo).ToList();
await registrationRepository.UpdateAsync(x => new EventRegistration
{
PaymentStatus = 4,
UpdatedTime = now
}, x => idsToCancel.Contains(x.Id));
if (orderNos.Any())
{
await orderRepository.UpdateAsync(x => new YD_Event.Core.Entities.Order
{
OrderStatus = OrderStatus.Cancelled,
UpdatedTime = now
}, x => orderNos.Contains(x.OrderNo));
}
_logger.LogInformation("已成功取消 {Count} 个超时未支付订单", expiredRegistrations.Count);
}
}
catch (Exception ex)
{
_logger.LogError(ex, "处理超时未支付订单时发生错误");
}
}
public override void Dispose()
{
_timer.Dispose();
base.Dispose();
}
}

View File

@ -37,6 +37,7 @@ public class UnifiedRegistrationService : ITransient
private readonly ISqlSugarRepository<YD_Event.Core.Entities.Merchant> _merchantRepository;
private readonly ISqlSugarRepository<YD_Event.Core.Entities.Coupon> _couponRepository;
private readonly ISqlSugarRepository<YD_Event.Core.Entities.UserCoupon> _userCouponRepository;
private readonly ISqlSugarRepository<YD_Event.Core.Entities.CouponRedemptionCode> _redemptionCodeRepository;
private readonly ISqlSugarRepository<EventRegistrationPayment> _paymentRepository;
private readonly ISqlSugarRepository<MerchantCertification> _merchantCertificationRepository;
private readonly ISqlSugarClient _db;
@ -66,6 +67,7 @@ public class UnifiedRegistrationService : ITransient
ISqlSugarRepository<YD_Event.Core.Entities.Merchant> merchantRepository,
ISqlSugarRepository<YD_Event.Core.Entities.Coupon> couponRepository,
ISqlSugarRepository<YD_Event.Core.Entities.UserCoupon> userCouponRepository,
ISqlSugarRepository<YD_Event.Core.Entities.CouponRedemptionCode> redemptionCodeRepository,
ISqlSugarRepository<EventRegistrationPayment> paymentRepository,
ISqlSugarRepository<MerchantCertification> merchantCertificationRepository,
ISqlSugarClient db,
@ -94,6 +96,7 @@ public class UnifiedRegistrationService : ITransient
_merchantRepository = merchantRepository;
_couponRepository = couponRepository;
_userCouponRepository = userCouponRepository;
_redemptionCodeRepository = redemptionCodeRepository;
_paymentRepository = paymentRepository;
_merchantCertificationRepository = merchantCertificationRepository;
_db = db;
@ -1512,4 +1515,137 @@ public class UnifiedRegistrationService : ITransient
return payConfig;
}
/// <summary>
/// 统一优惠券验证和使用方法
/// </summary>
[UnitOfWork]
public async Task<ValidateCouponOutput> ValidateAndUseCouponAsync(ValidateCouponInput input, long userId)
{
var result = new ValidateCouponOutput
{
IsValid = false,
InvalidReason = ""
};
// 查找兑换码
var redemptionCode = await _redemptionCodeRepository.GetFirstAsync(rc =>
rc.Code == input.CouponCode &&
rc.Status == 0 &&
!rc.DeleteMark);
if (redemptionCode == null)
{
result.InvalidReason = "兑换码不存在或已使用";
return result;
}
// 查找优惠券
var coupon = await _couponRepository.GetFirstAsync(c =>
c.Id == redemptionCode.CouponId &&
c.Status == AvailabilityStatus.Enable &&
!c.DeleteMark);
if (coupon == null)
{
result.InvalidReason = "优惠券不存在或已失效";
return result;
}
// 检查优惠券是否已过期
if (coupon.EndTime.HasValue && coupon.EndTime.Value < DateTime.Now)
{
result.InvalidReason = "优惠券已过期";
return result;
}
// 检查优惠券是否已开始
if (coupon.StartTime.HasValue && coupon.StartTime.Value > DateTime.Now)
{
result.InvalidReason = "优惠券尚未生效";
return result;
}
// 检查库存
if (coupon.TotalQuantity > 0 && coupon.ClaimedQuantity >= coupon.TotalQuantity)
{
result.InvalidReason = "优惠券已被领完";
return result;
}
// 检查优惠券是否适用于该赛事
if (!string.IsNullOrEmpty(coupon.EventIds))
{
var eventIds = coupon.EventIds.Split(',').Select(long.Parse).ToList();
if (!eventIds.Contains(input.EventId))
{
result.InvalidReason = "该优惠券不适用于当前赛事";
return result;
}
}
// 检查当前用户是否拥有该优惠券且未使用,或者可以直接领取
var userCoupon = await _userCouponRepository.GetFirstAsync(uc =>
uc.CouponId == coupon.Id &&
uc.UserId == userId &&
uc.Status == 0 &&
!uc.DeleteMark);
// 如果用户没有该优惠券,检查是否可以直接领取(假设可以直接通过兑换码领取)
if (userCoupon == null)
{
// 创建用户优惠券记录
userCoupon = new YD_Event.Core.Entities.UserCoupon
{
Id = _idGenerator.NewLong(),
UserId = userId,
CouponId = coupon.Id,
ClaimTime = DateTime.Now,
Status = 0,
RedemptionCodeId = redemptionCode.Id,
RedemptionCode = redemptionCode.Code,
CreatedUserId = userId,
CreatedTime = DateTime.Now,
DeleteMark = false
};
await _userCouponRepository.InsertAsync(userCoupon);
// 更新兑换码状态和关联
redemptionCode.Status = 1;
redemptionCode.UsedTime = DateTime.Now;
redemptionCode.UserCouponId = userCoupon.Id;
await _redemptionCodeRepository.UpdateAsync(redemptionCode);
// 更新优惠券已领取数量
coupon.ClaimedQuantity += 1;
await _couponRepository.UpdateAsync(coupon);
}
// 计算优惠金额
decimal discountAmount = 0;
decimal finalAmount = input.OriginalAmount;
// Type: 0-折扣, 1-定额
if (coupon.Type == 0) // 折扣
{
discountAmount = input.OriginalAmount * (1 - coupon.Value / 100);
}
else if (coupon.Type == 1) // 定额
{
discountAmount = Math.Min(coupon.Value, input.OriginalAmount);
}
finalAmount = input.OriginalAmount - discountAmount;
finalAmount = Math.Max(0, finalAmount);
result.IsValid = true;
result.CouponId = coupon.Id;
result.CouponName = coupon.Name ?? "优惠券";
result.DiscountType = coupon.Type;
result.DiscountValue = coupon.Value;
result.DiscountAmount = discountAmount;
result.FinalAmount = finalAmount;
return result;
}
}

View File

@ -801,132 +801,9 @@ public class UserEventRegistrationService : IDynamicApiController, ITransient
[HttpPost("coupon/validate")]
public async Task<ValidateCouponOutput> ValidateCoupon([FromBody] ValidateCouponInput input)
{
var result = new ValidateCouponOutput
{
IsValid = false,
InvalidReason = ""
};
// 查找兑换码
var redemptionCode = await _redemptionCodeRepository.GetFirstAsync(rc =>
rc.Code == input.CouponCode &&
rc.Status == 0 && // 0-未使用
!rc.DeleteMark);
if (redemptionCode == null)
{
result.InvalidReason = "兑换码不存在或已使用";
return result;
}
// 查找优惠券
var coupon = await _couponRepository.GetFirstAsync(c =>
c.Id == redemptionCode.CouponId &&
c.Status == AvailabilityStatus.Enable &&
!c.DeleteMark);
if (coupon == null)
{
result.InvalidReason = "优惠券不存在或已失效";
return result;
}
// 检查优惠券是否已过期
if (coupon.EndTime.HasValue && coupon.EndTime.Value < DateTime.Now)
{
result.InvalidReason = "优惠券已过期";
return result;
}
// 检查优惠券是否已开始
if (coupon.StartTime.HasValue && coupon.StartTime.Value > DateTime.Now)
{
result.InvalidReason = "优惠券尚未生效";
return result;
}
// 检查库存
if (coupon.TotalQuantity > 0 && coupon.ClaimedQuantity >= coupon.TotalQuantity)
{
result.InvalidReason = "优惠券已被领完";
return result;
}
// 检查优惠券是否适用于该赛事
if (!string.IsNullOrEmpty(coupon.EventIds))
{
var eventIds = coupon.EventIds.Split(',').Select(long.Parse).ToList();
if (!eventIds.Contains(input.EventId))
{
result.InvalidReason = "该优惠券不适用于当前赛事";
return result;
}
}
// 检查当前用户是否拥有该优惠券且未使用,或者可以直接领取
var userId = _authManager.UserId;
var userCoupon = await _userCouponRepository.GetFirstAsync(uc =>
uc.CouponId == coupon.Id &&
uc.UserId == userId &&
uc.Status == 0 && // 0-未使用
!uc.DeleteMark);
// 如果用户没有该优惠券,检查是否可以直接领取(假设可以直接通过兑换码领取)
if (userCoupon == null)
{
// 创建用户优惠券记录
userCoupon = new YD_Event.Core.Entities.UserCoupon
{
Id = _idGenerator.NewLong(),
UserId = userId,
CouponId = coupon.Id,
ClaimTime = DateTime.Now,
Status = 0, // 未使用
RedemptionCodeId = redemptionCode.Id,
RedemptionCode = redemptionCode.Code,
CreatedUserId = userId,
CreatedTime = DateTime.Now,
DeleteMark = false
};
await _userCouponRepository.InsertAsync(userCoupon);
// 更新兑换码状态和关联
redemptionCode.Status = 1; // 已使用
redemptionCode.UsedTime = DateTime.Now;
redemptionCode.UserCouponId = userCoupon.Id;
await _redemptionCodeRepository.UpdateAsync(redemptionCode);
// 更新优惠券已领取数量
coupon.ClaimedQuantity += 1;
await _couponRepository.UpdateAsync(coupon);
}
// 计算优惠金额
decimal discountAmount = 0;
decimal finalAmount = input.OriginalAmount;
// Type: 0-折扣, 1-定额
if (coupon.Type == 0) // 折扣
{
discountAmount = input.OriginalAmount * (1 - coupon.Value / 100);
}
else if (coupon.Type == 1) // 定额
{
discountAmount = Math.Min(coupon.Value, input.OriginalAmount);
}
finalAmount = input.OriginalAmount - discountAmount;
finalAmount = Math.Max(0, finalAmount); // 确保不小于0
result.IsValid = true;
result.CouponId = coupon.Id;
result.CouponName = coupon.Name ?? "优惠券";
result.DiscountType = coupon.Type;
result.DiscountValue = coupon.Value;
result.DiscountAmount = discountAmount;
result.FinalAmount = finalAmount;
return result;
return await _unifiedRegistrationService.ValidateAndUseCouponAsync(input, userId);
}
private string GetPaymentStatusDesc(int status)

View File

@ -263,6 +263,11 @@
身份证图片URL
</summary>
</member>
<member name="P:YD_Event.Application.AppUser.Dtos.CreateOrUpdateMemberInput.IdCardBackImageUrl">
<summary>
身份证背面图片URL
</summary>
</member>
<member name="P:YD_Event.Application.AppUser.Dtos.CreateOrUpdateMemberInput.PassportPhotoUrl">
<summary>
护照图片URL
@ -353,6 +358,11 @@
身份证图片URL
</summary>
</member>
<member name="P:YD_Event.Application.AppUser.Dtos.AppUserOutput.IdCardBackImageUrl">
<summary>
身份证背面图片URL
</summary>
</member>
<member name="P:YD_Event.Application.AppUser.Dtos.AppUserOutput.PassportPhotoUrl">
<summary>
护照图片URL
@ -2780,6 +2790,11 @@
身份证正面图片(认证类型1时使用)
</summary>
</member>
<member name="P:YD_Event.Application.Client.Dtos.MyAuthInfoDto.IdCardBackImageUrl">
<summary>
身份证背面图片(认证类型1时使用)
</summary>
</member>
<member name="P:YD_Event.Application.Client.Dtos.MyAuthInfoDto.PassportPhotoUrl">
<summary>
护照图片(认证类型2时使用)
@ -2840,6 +2855,11 @@
身份证正面图片(认证类型1时必填)
</summary>
</member>
<member name="P:YD_Event.Application.Client.Dtos.SubmitAuthInput.IdCardBackImageUrl">
<summary>
身份证背面图片(认证类型1时必填)
</summary>
</member>
<member name="P:YD_Event.Application.Client.Dtos.SubmitAuthInput.PassportPhotoUrl">
<summary>
护照图片(认证类型2时必填)
@ -7344,6 +7364,11 @@
<param name="now">当前时间</param>
<returns>计算后的赛事状态</returns>
</member>
<member name="T:YD_Event.Application.Event.PaymentExpirationBackgroundService">
<summary>
支付超时处理后台服务
</summary>
</member>
<member name="T:YD_Event.Application.Event.UnifiedRegistrationService">
<summary>
统一报名处理服务
@ -7459,6 +7484,11 @@
根据赛事获取对应的微信支付配置
</summary>
</member>
<member name="M:YD_Event.Application.Event.UnifiedRegistrationService.ValidateAndUseCouponAsync(YD_Event.Application.Event.Dtos.ValidateCouponInput,System.Int64)">
<summary>
统一优惠券验证和使用方法
</summary>
</member>
<member name="T:YD_Event.Application.Event.UserEventRecordService">
<summary>
用户端参赛记录管理

View File

@ -85,11 +85,17 @@ public class AppUser : Entity<long>, ISoftDelete, ICreatedUserId, ICreatedTime,
public string? IdCardNumber { get; set; }
/// <summary>
/// 身份证图片URL
/// 身份证正面图片URL
/// </summary>
[SugarColumn(Length = 512, IsNullable = true)]
public string? IdCardImageUrl { get; set; }
/// <summary>
/// 身份证背面图片URL
/// </summary>
[SugarColumn(Length = 512, IsNullable = true)]
public string? IdCardBackImageUrl { get; set; }
/// <summary>
/// 护照图片URL
/// </summary>

View File

@ -0,0 +1,17 @@
namespace YD_Event.Core.Enum;
/// <summary>
/// 优惠券类型
/// </summary>
public enum CouponType
{
/// <summary>
/// 折扣
/// </summary>
Discount = 0,
/// <summary>
/// 定额
/// </summary>
FixedAmount = 1
}

View File

@ -601,7 +601,12 @@
</member>
<member name="P:YD_Event.Core.Entities.AppUser.IdCardImageUrl">
<summary>
身份证图片URL
身份证正面图片URL
</summary>
</member>
<member name="P:YD_Event.Core.Entities.AppUser.IdCardBackImageUrl">
<summary>
身份证背面图片URL
</summary>
</member>
<member name="P:YD_Event.Core.Entities.AppUser.PassportPhotoUrl">
@ -4272,6 +4277,21 @@
单价(项目)
</summary>
</member>
<member name="T:YD_Event.Core.Enum.CouponType">
<summary>
优惠券类型
</summary>
</member>
<member name="F:YD_Event.Core.Enum.CouponType.Discount">
<summary>
折扣
</summary>
</member>
<member name="F:YD_Event.Core.Enum.CouponType.FixedAmount">
<summary>
定额
</summary>
</member>
<member name="T:YD_Event.Core.Enum.CoverType">
<summary>
博客封面类型

View File

@ -35,6 +35,9 @@ public class Startup : AppStartup
// 注册赛事状态自动更新后台服务
services.AddHostedService<YD_Event.Application.Event.EventStatusUpdateService>();
// 注册支付超时处理后台服务
services.AddHostedService<YD_Event.Application.Event.PaymentExpirationBackgroundService>();
//解决Encoding无法使用gb2312编码问题
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);