fengling-member-service/src/Fengling.Member.Application/Services/PointsProcessingService.cs
sam 92247346dd refactor: major project restructuring and cleanup
Changes:

- Remove deprecated Fengling.Activity and YarpGateway.Admin projects

- Add points processing services with distributed lock support

- Update Vben frontend with gateway management pages

- Add gateway config controller and database listener

- Update routing to use header-mixed-nav layout

- Add comprehensive test suites for Member services

- Add YarpGateway integration tests

- Update package versions in Directory.Packages.props

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-02-15 10:34:07 +08:00

131 lines
4.9 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using Fengling.Member.Domain.Aggregates.PointsModel;
using Fengling.Member.Domain.Events.Points;
using Fengling.Member.Infrastructure;
using Microsoft.Extensions.Logging;
namespace Fengling.Member.Application.Services;
/// <summary>
/// 积分处理服务实现
/// 使用分布式锁 + Redis 缓存实现高性能积分处理
/// </summary>
public class PointsProcessingService : IPointsProcessingService
{
private readonly IPointsAccountCache _cache;
private readonly ICodeDistributedLock _lock;
private readonly IPointsHistoryRepository _historyRepository;
private readonly IMediator _mediator;
private readonly ITenantAccessor _tenantAccessor;
private readonly ILogger<PointsProcessingService> _logger;
public PointsProcessingService(
IPointsAccountCache cache,
ICodeDistributedLock distributedLock,
IPointsHistoryRepository historyRepository,
IMediator mediator,
ITenantAccessor tenantAccessor,
ILogger<PointsProcessingService> logger)
{
_cache = cache;
_lock = distributedLock;
_historyRepository = historyRepository;
_mediator = mediator;
_tenantAccessor = tenantAccessor;
_logger = logger;
}
public async Task<PointsProcessResult> ProcessAsync(PointsProcessRequest request, CancellationToken ct = default)
{
// 1. 幂等性检查 - 检查码是否已处理
if (await _cache.IsCodeProcessedAsync(request.CodeId))
{
_logger.LogWarning("Code {CodeId} already processed", request.CodeId);
return PointsProcessResult.Failed("码已处理", "CODE_ALREADY_PROCESSED");
}
// 2. 获取分布式锁
var lockResult = await _lock.AcquireAsync(request.CodeId);
if (!lockResult.Success)
{
_logger.LogWarning("Failed to acquire lock for code {CodeId}: {Error}", request.CodeId, lockResult.ErrorMessage);
return PointsProcessResult.Failed("处理中,请稍后重试", "LOCK_FAILED");
}
try
{
// 3. 双重检查幂等性(在获取锁后再次检查)
if (await _cache.IsCodeProcessedAsync(request.CodeId))
{
return PointsProcessResult.Failed("码已处理", "CODE_ALREADY_PROCESSED");
}
// 4. 从缓存获取账户,不存在则创建
var account = await _cache.GetAsync(request.MemberId);
var tenantId = _tenantAccessor.GetTenantId() ?? 0;
if (account == null)
{
account = new PointsAccountCacheModel
{
MemberId = request.MemberId,
TenantId = tenantId,
TotalPoints = 0,
FrozenPoints = 0
};
}
// 5. 处理积分
int newTotal;
if (request.IsAddition)
{
newTotal = await _cache.AddPointsAsync(request.MemberId, request.Points);
}
else
{
var deductResult = await _cache.DeductPointsAsync(request.MemberId, request.Points);
if (!deductResult.Success)
{
return PointsProcessResult.Failed("积分不足", "INSUFFICIENT_POINTS");
}
newTotal = deductResult.RemainingPoints;
}
// 6. 标记码已处理
await _cache.MarkCodeProcessedAsync(request.CodeId);
// 7. 发布领域事件(异步持久化到数据库)
var domainEvent = new PointsChangedEvent(
0, // AccountId - 缓存中暂无ID事件消费者处理
request.MemberId,
tenantId,
request.IsAddition ? request.Points : -request.Points,
newTotal,
request.TransactionType,
request.SourceId,
request.Remark);
await _mediator.Publish(domainEvent, ct);
_logger.LogInformation(
"Points processed successfully: MemberId={MemberId}, CodeId={CodeId}, Points={Points}, IsAddition={IsAddition}, NewTotal={Total}",
request.MemberId, request.CodeId, request.Points, request.IsAddition, newTotal);
return PointsProcessResult.Succeeded(request.Points, newTotal);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error processing points for member {MemberId}, code {CodeId}", request.MemberId, request.CodeId);
return PointsProcessResult.Failed("处理失败", "PROCESS_ERROR");
}
finally
{
// 8. 释放锁
await _lock.ReleaseAsync(request.CodeId, lockResult.LockValue!);
}
}
public async Task<PointsAccountCacheModel?> GetBalanceAsync(long memberId)
{
return await _cache.GetAsync(memberId);
}
}