diff --git a/AGENTS.md b/AGENTS.md index 0990858..e476934 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -56,10 +56,10 @@ var user = await _repo.GetAsync(id); if (user == null) return NotFound(); - // 2. (空行后) 扣减余额逻辑 + // 2. 扣减余额逻辑 user.Balance -= amount; - // 3. (空行后) 保存更改 + // 3. 保存更改 await _unitOfWork.SaveChangesAsync(); ``` * **Swagger**:必须开启 JWT 鉴权按钮,Request/Response 示例必须清晰。 diff --git a/scripts/build-adminapi-forlinux b/scripts/build-adminapi-forlinux new file mode 100755 index 0000000..cfadd23 --- /dev/null +++ b/scripts/build-adminapi-forlinux @@ -0,0 +1,35 @@ +#!/usr/bin/env bash +# 用法:在 Linux 终端执行本脚本,自动构建并重启 AdminApi 容器。 +# 前置:已安装并运行 Docker。 +set -euo pipefail +# 0. 遇到异常时输出错误信息,方便查看 +trap 'echo "发生错误:${BASH_COMMAND}" >&2' ERR +# 1. 基本变量(脚本位于 repo_root/scripts,下移一层再上跳到仓库根) +script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +repo_root="$(cd "${script_dir}/.." && pwd)" +image_name='takeout.api.admin:dev' +container_name='takeout.api.admin' +dockerfile_path="${repo_root}/src/Api/TakeoutSaaS.AdminApi/Dockerfile" +echo "工作目录:${repo_root}" +# 2. 停止并删除旧容器 +if docker ps -a --format '{{.Names}}' | grep -qx "${container_name}"; then + echo "发现旧容器,正在移除:${container_name}" + docker stop "${container_name}" >/dev/null + docker rm "${container_name}" >/dev/null +fi +# 3. 删除旧镜像 +if docker images --format '{{.Repository}}:{{.Tag}}' | grep -qx "${image_name}"; then + echo "发现旧镜像,正在移除:${image_name}" + docker rmi "${image_name}" >/dev/null +fi +# 4. 构建最新镜像(使用仓库根作为上下文) +echo "开始构建镜像:${image_name}" +docker build -f "${dockerfile_path}" -t "${image_name}" "${repo_root}" +# 5. 运行新容器并映射端口 +echo "运行新容器:${container_name} (端口映射 7801:7801,环境 Development)" +docker run -d --name "${container_name}" -e ASPNETCORE_ENVIRONMENT=Development -p 7801:7801 "${image_name}" +echo "完成。镜像:${image_name},容器:${container_name}。Swagger 访问:http://localhost:7801/swagger" +# 6. 交互式终端下暂停,方便查看输出 +if [ -t 0 ]; then + read -r -p "按回车关闭窗口" _ +fi diff --git a/src/Api/TakeoutSaaS.AdminApi/Controllers/BillingsController.cs b/src/Api/TakeoutSaaS.AdminApi/Controllers/BillingsController.cs index e1083c5..b4aa0e8 100644 --- a/src/Api/TakeoutSaaS.AdminApi/Controllers/BillingsController.cs +++ b/src/Api/TakeoutSaaS.AdminApi/Controllers/BillingsController.cs @@ -173,10 +173,10 @@ public sealed class BillingsController(IMediator mediator) : BaseApiController // 1. 绑定账单标识 command = command with { BillingId = id }; - // 2. (空行后) 一键确认收款(含:写入 VerifiedBy/VerifiedAt,并同步更新账单已收金额/状态) + // 2. 一键确认收款(含:写入 VerifiedBy/VerifiedAt,并同步更新账单已收金额/状态) var result = await mediator.Send(command, cancellationToken); - // 3. (空行后) 返回结果 + // 3. 返回结果 return ApiResponse.Ok(result); } @@ -196,10 +196,10 @@ public sealed class BillingsController(IMediator mediator) : BaseApiController // 1. 绑定支付记录标识 command = command with { PaymentId = paymentId }; - // 2. (空行后) 审核支付记录 + // 2. 审核支付记录 var result = await mediator.Send(command, cancellationToken); - // 3. (空行后) 返回审核结果 + // 3. 返回审核结果 return ApiResponse.Ok(result); } @@ -236,18 +236,18 @@ public sealed class BillingsController(IMediator mediator) : BaseApiController // 1. 执行导出 var bytes = await mediator.Send(query, cancellationToken); - // 2. (空行后) 解析格式并生成文件名 + // 2. 解析格式并生成文件名 var extension = ResolveExportFileExtension(query.Format); var fileName = $"billings_{DateTime.UtcNow:yyyyMMdd_HHmmss}.{extension}"; - // 3. (空行后) 显式写入 Content-Disposition,确保浏览器以附件形式下载 + // 3. 显式写入 Content-Disposition,确保浏览器以附件形式下载 Response.Headers[HeaderNames.ContentDisposition] = new ContentDispositionHeaderValue("attachment") { FileName = fileName, FileNameStar = fileName }.ToString(); - // 4. (空行后) 返回二进制流(统一 octet-stream,避免被默认 JSON Produces 影响) + // 4. 返回二进制流(统一 octet-stream,避免被默认 JSON Produces 影响) return File(bytes, "application/octet-stream"); } @@ -292,7 +292,7 @@ public sealed class BillingsController(IMediator mediator) : BaseApiController // 1. 归一化导出格式 var normalized = (format ?? string.Empty).Trim(); - // 2. (空行后) 映射扩展名 + // 2. 映射扩展名 return normalized.ToUpperInvariant() switch { "PDF" => "pdf", diff --git a/src/Api/TakeoutSaaS.AdminApi/Controllers/TenantsController.cs b/src/Api/TakeoutSaaS.AdminApi/Controllers/TenantsController.cs index 412291a..3108448 100644 --- a/src/Api/TakeoutSaaS.AdminApi/Controllers/TenantsController.cs +++ b/src/Api/TakeoutSaaS.AdminApi/Controllers/TenantsController.cs @@ -105,11 +105,11 @@ public sealed class TenantsController(IMediator mediator) : BaseApiController return ApiResponse.Error(StatusCodes.Status400BadRequest, "路由 tenantId 与请求体 tenantId 不一致"); } - // 2. (空行后) 绑定租户标识并执行更新(若不存在或冲突则抛出业务异常,由全局异常处理转换为 404/409) + // 2. 绑定租户标识并执行更新(若不存在或冲突则抛出业务异常,由全局异常处理转换为 404/409) var command = body with { TenantId = tenantId }; await mediator.Send(command, cancellationToken); - // 3. (空行后) 返回成功结果 + // 3. 返回成功结果 return ApiResponse.Ok(null); } @@ -377,7 +377,7 @@ public sealed class TenantsController(IMediator mediator) : BaseApiController // 1. 生成一次性令牌 var token = await mediator.Send(new CreateTenantAdminResetLinkTokenCommand { TenantId = tenantId }, cancellationToken); - // 2. (空行后) 解析前端来源(优先 Origin,避免拼成 AdminApi 域名) + // 2. 解析前端来源(优先 Origin,避免拼成 AdminApi 域名) var origin = Request.Headers.Origin.ToString(); if (string.IsNullOrWhiteSpace(origin)) { @@ -387,7 +387,7 @@ public sealed class TenantsController(IMediator mediator) : BaseApiController origin = origin.TrimEnd('/'); var resetUrl = $"{origin}/#/auth/reset-password?token={Uri.EscapeDataString(token)}"; - // 3. (空行后) 返回链接 + // 3. 返回链接 return ApiResponse.Ok(data: resetUrl); } @@ -430,10 +430,10 @@ public sealed class TenantsController(IMediator mediator) : BaseApiController // 1. 绑定租户标识 query = query with { TenantId = tenantId }; - // 2. (空行后) 查询配额使用历史 + // 2. 查询配额使用历史 var result = await mediator.Send(query, cancellationToken); - // 3. (空行后) 返回分页结果 + // 3. 返回分页结果 return ApiResponse>.Ok(result); } } diff --git a/src/Api/TakeoutSaaS.AdminApi/Controllers/UsersController.cs b/src/Api/TakeoutSaaS.AdminApi/Controllers/UsersController.cs index 412e8e9..68a3411 100644 --- a/src/Api/TakeoutSaaS.AdminApi/Controllers/UsersController.cs +++ b/src/Api/TakeoutSaaS.AdminApi/Controllers/UsersController.cs @@ -109,10 +109,10 @@ public sealed class UsersController(IMediator mediator) : BaseApiController command = command with { UserId = userId }; } - // 2. (空行后) 执行更新 + // 2. 执行更新 var result = await mediator.Send(command, cancellationToken); - // 3. (空行后) 返回结果或 404 + // 3. 返回结果或 404 return result == null ? ApiResponse.Error(ErrorCodes.NotFound, "用户不存在") : ApiResponse.Ok(result); @@ -169,10 +169,10 @@ public sealed class UsersController(IMediator mediator) : BaseApiController command = command with { UserId = userId }; } - // 2. (空行后) 执行状态变更 + // 2. 执行状态变更 var result = await mediator.Send(command, cancellationToken); - // 3. (空行后) 返回结果或 404 + // 3. 返回结果或 404 return result ? ApiResponse.Success() : ApiResponse.Error(ErrorCodes.NotFound, "用户不存在"); diff --git a/src/Application/TakeoutSaaS.Application/App/Billings/Handlers/CancelBillingCommandHandler.cs b/src/Application/TakeoutSaaS.Application/App/Billings/Handlers/CancelBillingCommandHandler.cs index 7aa0255..c1a8da3 100644 --- a/src/Application/TakeoutSaaS.Application/App/Billings/Handlers/CancelBillingCommandHandler.cs +++ b/src/Application/TakeoutSaaS.Application/App/Billings/Handlers/CancelBillingCommandHandler.cs @@ -23,10 +23,10 @@ public sealed class CancelBillingCommandHandler( throw new BusinessException(ErrorCodes.NotFound, "账单不存在"); } - // 2. (空行后) 取消账单(领域规则校验在实体方法内) + // 2. 取消账单(领域规则校验在实体方法内) billing.Cancel(request.Reason); - // 3. (空行后) 持久化 + // 3. 持久化 await billingRepository.UpdateAsync(billing, cancellationToken); await billingRepository.SaveChangesAsync(cancellationToken); diff --git a/src/Application/TakeoutSaaS.Application/App/Billings/Handlers/ConfirmPaymentCommandHandler.cs b/src/Application/TakeoutSaaS.Application/App/Billings/Handlers/ConfirmPaymentCommandHandler.cs index c6ebbda..57a36d1 100644 --- a/src/Application/TakeoutSaaS.Application/App/Billings/Handlers/ConfirmPaymentCommandHandler.cs +++ b/src/Application/TakeoutSaaS.Application/App/Billings/Handlers/ConfirmPaymentCommandHandler.cs @@ -30,14 +30,14 @@ public sealed class ConfirmPaymentCommandHandler( throw new BusinessException(ErrorCodes.Unauthorized, "未登录或无效的操作者身份"); } - // 2. (空行后) 查询账单 + // 2. 查询账单 var billing = await billingRepository.FindByIdAsync(request.BillingId, cancellationToken); if (billing is null) { throw new BusinessException(ErrorCodes.NotFound, "账单不存在"); } - // 3. (空行后) 业务规则检查 + // 3. 业务规则检查 if (billing.Status == TenantBillingStatus.Paid) { throw new BusinessException(ErrorCodes.BusinessError, "已支付账单不允许重复收款"); @@ -48,7 +48,7 @@ public sealed class ConfirmPaymentCommandHandler( throw new BusinessException(ErrorCodes.BusinessError, "已取消账单不允许收款"); } - // 4. (空行后) 金额边界:不允许超过剩余应收(与前端校验保持一致) + // 4. 金额边界:不允许超过剩余应收(与前端校验保持一致) var totalAmount = billing.CalculateTotalAmount(); var remainingAmount = totalAmount - billing.AmountPaid; if (request.Amount > remainingAmount) @@ -56,7 +56,7 @@ public sealed class ConfirmPaymentCommandHandler( throw new BusinessException(ErrorCodes.BadRequest, "支付金额不能超过剩余应收"); } - // 5. (空行后) 幂等校验:交易号唯一 + // 5. 幂等校验:交易号唯一 if (!string.IsNullOrWhiteSpace(request.TransactionNo)) { var exists = await paymentRepository.GetByTransactionNoAsync(request.TransactionNo.Trim(), cancellationToken); @@ -66,7 +66,7 @@ public sealed class ConfirmPaymentCommandHandler( } } - // 6. (空行后) 构建支付记录并立即审核通过 + // 6. 构建支付记录并立即审核通过 var now = DateTime.UtcNow; var payment = new TenantPayment { @@ -84,15 +84,15 @@ public sealed class ConfirmPaymentCommandHandler( payment.Verify(currentUserAccessor.UserId); - // 7. (空行后) 同步更新账单已收金额/状态(支持分次收款) + // 7. 同步更新账单已收金额/状态(支持分次收款) billing.MarkAsPaid(payment.Amount, payment.TransactionNo ?? string.Empty); - // 8. (空行后) 持久化变更(同一 DbContext 下单次 SaveChanges 可提交两张表) + // 8. 持久化变更(同一 DbContext 下单次 SaveChanges 可提交两张表) await paymentRepository.AddAsync(payment, cancellationToken); await billingRepository.UpdateAsync(billing, cancellationToken); await paymentRepository.SaveChangesAsync(cancellationToken); - // 9. (空行后) 返回 DTO + // 9. 返回 DTO return payment.ToPaymentRecordDto(); } } diff --git a/src/Application/TakeoutSaaS.Application/App/Billings/Handlers/CreateBillingCommandHandler.cs b/src/Application/TakeoutSaaS.Application/App/Billings/Handlers/CreateBillingCommandHandler.cs index 3243dcd..8c8727d 100644 --- a/src/Application/TakeoutSaaS.Application/App/Billings/Handlers/CreateBillingCommandHandler.cs +++ b/src/Application/TakeoutSaaS.Application/App/Billings/Handlers/CreateBillingCommandHandler.cs @@ -30,7 +30,7 @@ public sealed class CreateBillingCommandHandler( throw new BusinessException(ErrorCodes.NotFound, "租户不存在"); } - // 2. (空行后) 构建账单实体 + // 2. 构建账单实体 var now = DateTime.UtcNow; var statementNo = $"BIL-{now:yyyyMMdd}-{idGenerator.NextId()}"; var lineItemsJson = JsonSerializer.Serialize(request.LineItems); @@ -54,11 +54,11 @@ public sealed class CreateBillingCommandHandler( Notes = request.Notes }; - // 3. (空行后) 持久化账单 + // 3. 持久化账单 await billingRepository.AddAsync(billing, cancellationToken); await billingRepository.SaveChangesAsync(cancellationToken); - // 4. (空行后) 返回详情 DTO + // 4. 返回详情 DTO return billing.ToBillingDetailDto([], tenant.Name); } } diff --git a/src/Application/TakeoutSaaS.Application/App/Billings/Handlers/ExportBillingsQueryHandler.cs b/src/Application/TakeoutSaaS.Application/App/Billings/Handlers/ExportBillingsQueryHandler.cs index 1309c2a..7731d80 100644 --- a/src/Application/TakeoutSaaS.Application/App/Billings/Handlers/ExportBillingsQueryHandler.cs +++ b/src/Application/TakeoutSaaS.Application/App/Billings/Handlers/ExportBillingsQueryHandler.cs @@ -24,14 +24,14 @@ public sealed class ExportBillingsQueryHandler( throw new BusinessException(ErrorCodes.BadRequest, "账单 ID 列表不能为空"); } - // 2. (空行后) 查询账单数据 + // 2. 查询账单数据 var billings = await billingRepository.GetByIdsAsync(request.BillingIds, cancellationToken); if (billings.Count == 0) { throw new BusinessException(ErrorCodes.NotFound, "未找到任何匹配的账单"); } - // 3. (空行后) 根据格式导出 + // 3. 根据格式导出 var format = (request.Format ?? string.Empty).Trim().ToLowerInvariant(); return format switch { diff --git a/src/Application/TakeoutSaaS.Application/App/Billings/Handlers/GenerateSubscriptionBillingCommandHandler.cs b/src/Application/TakeoutSaaS.Application/App/Billings/Handlers/GenerateSubscriptionBillingCommandHandler.cs index 2dc97e7..d92b089 100644 --- a/src/Application/TakeoutSaaS.Application/App/Billings/Handlers/GenerateSubscriptionBillingCommandHandler.cs +++ b/src/Application/TakeoutSaaS.Application/App/Billings/Handlers/GenerateSubscriptionBillingCommandHandler.cs @@ -30,7 +30,7 @@ public sealed class GenerateSubscriptionBillingCommandHandler( throw new BusinessException(ErrorCodes.NotFound, "订阅不存在"); } - // 2. (空行后) 校验套餐价格信息 + // 2. 校验套餐价格信息 var subscription = detail.Subscription; var package = detail.Package; if (package is null) @@ -38,7 +38,7 @@ public sealed class GenerateSubscriptionBillingCommandHandler( throw new BusinessException(ErrorCodes.BusinessError, "订阅未关联有效套餐,无法生成账单"); } - // 3. (空行后) 按订阅周期选择价格(简化规则:优先按年/按月) + // 3. 按订阅周期选择价格(简化规则:优先按年/按月) var billingPeriodDays = (subscription.EffectiveTo - subscription.EffectiveFrom).TotalDays; var amountDue = billingPeriodDays >= 300 ? package.YearlyPrice @@ -49,14 +49,14 @@ public sealed class GenerateSubscriptionBillingCommandHandler( throw new BusinessException(ErrorCodes.BusinessError, "套餐价格未配置,无法生成账单"); } - // 4. (空行后) 幂等校验:同一周期开始时间仅允许存在一张未取消账单 + // 4. 幂等校验:同一周期开始时间仅允许存在一张未取消账单 var exists = await billingRepository.ExistsNotCancelledByPeriodStartAsync(subscription.TenantId, subscription.EffectiveFrom, cancellationToken); if (exists) { throw new BusinessException(ErrorCodes.Conflict, "该订阅周期的账单已存在"); } - // 5. (空行后) 构建账单实体 + // 5. 构建账单实体 var now = DateTime.UtcNow; var statementNo = $"BIL-{now:yyyyMMdd}-{idGenerator.NextId()}"; var lineItems = new List @@ -91,11 +91,11 @@ public sealed class GenerateSubscriptionBillingCommandHandler( Notes = subscription.Notes }; - // 6. (空行后) 持久化账单 + // 6. 持久化账单 await billingRepository.AddAsync(billing, cancellationToken); await billingRepository.SaveChangesAsync(cancellationToken); - // 7. (空行后) 返回详情 DTO + // 7. 返回详情 DTO return billing.ToBillingDetailDto([], detail.TenantName); } } diff --git a/src/Application/TakeoutSaaS.Application/App/Billings/Handlers/GetBillingDetailQueryHandler.cs b/src/Application/TakeoutSaaS.Application/App/Billings/Handlers/GetBillingDetailQueryHandler.cs index 23d7e21..cb9ae96 100644 --- a/src/Application/TakeoutSaaS.Application/App/Billings/Handlers/GetBillingDetailQueryHandler.cs +++ b/src/Application/TakeoutSaaS.Application/App/Billings/Handlers/GetBillingDetailQueryHandler.cs @@ -46,7 +46,7 @@ public sealed class GetBillingDetailQueryHandler( throw new BusinessException(ErrorCodes.NotFound, "账单不存在"); } - // 1.2 (空行后) 读取账单行数据到内存(释放 Reader,避免同连接并发执行命令) + // 1.2 读取账单行数据到内存(释放 Reader,避免同连接并发执行命令) var billingId = billReader.GetInt64(0); var tenantId = billReader.GetInt64(1); var tenantName = billReader.IsDBNull(2) ? string.Empty : billReader.GetString(2); @@ -71,10 +71,10 @@ public sealed class GetBillingDetailQueryHandler( DateTime? updatedAt = billReader.IsDBNull(21) ? null : billReader.GetDateTime(21); long? updatedBy = billReader.IsDBNull(22) ? null : billReader.GetInt64(22); - // 1.3 (空行后) 主动释放账单 Reader,确保后续查询不会触发 Npgsql 并发命令异常 + // 1.3 主动释放账单 Reader,确保后续查询不会触发 Npgsql 并发命令异常 await billReader.DisposeAsync(); - // 1.4 (空行后) 反序列化账单明细 + // 1.4 反序列化账单明细 var lineItems = new List(); if (!string.IsNullOrWhiteSpace(lineItemsJson)) { @@ -88,7 +88,7 @@ public sealed class GetBillingDetailQueryHandler( } } - // 1.5 (空行后) 查询支付记录 + // 1.5 查询支付记录 var payments = new List(); await using var paymentCommand = CreateCommand( connection, @@ -121,7 +121,7 @@ public sealed class GetBillingDetailQueryHandler( }); } - // 1.6 (空行后) 组装详情 DTO + // 1.6 组装详情 DTO var totalAmount = amountDue - discountAmount + taxAmount; return new BillingDetailDto diff --git a/src/Application/TakeoutSaaS.Application/App/Billings/Handlers/GetBillingListQueryHandler.cs b/src/Application/TakeoutSaaS.Application/App/Billings/Handlers/GetBillingListQueryHandler.cs index 7d5fca8..894e644 100644 --- a/src/Application/TakeoutSaaS.Application/App/Billings/Handlers/GetBillingListQueryHandler.cs +++ b/src/Application/TakeoutSaaS.Application/App/Billings/Handlers/GetBillingListQueryHandler.cs @@ -33,13 +33,13 @@ public sealed class GetBillingListQueryHandler( var maxAmount = request.MaxAmount; var offset = (page - 1) * pageSize; - // 1.1 (空行后) 金额区间规范化(避免 min > max 导致结果为空) + // 1.1 金额区间规范化(避免 min > max 导致结果为空) if (minAmount.HasValue && maxAmount.HasValue && minAmount.Value > maxAmount.Value) { (minAmount, maxAmount) = (maxAmount, minAmount); } - // 2. (空行后) 排序白名单(防 SQL 注入) + // 2. 排序白名单(防 SQL 注入) var orderBy = request.SortBy?.Trim() switch { "DueDate" => "b.\"DueDate\"", @@ -50,7 +50,7 @@ public sealed class GetBillingListQueryHandler( _ => "b.\"CreatedAt\"" }; - // 3. (空行后) 查询总数 + 列表 + // 3. 查询总数 + 列表 return await dapperExecutor.QueryAsync( DatabaseConstants.AppDataSource, DatabaseConnectionRole.Read, @@ -72,7 +72,7 @@ public sealed class GetBillingListQueryHandler( ], token); - // 3.2 (空行后) 查询列表 + // 3.2 查询列表 var listSql = BuildListSql(orderBy, request.SortDesc); await using var listCommand = CreateCommand( connection, @@ -102,7 +102,7 @@ public sealed class GetBillingListQueryHandler( var taxAmount = reader.GetDecimal(10); var totalAmount = amountDue - discountAmount + taxAmount; - // 3.2.1 (空行后) 逾期辅助字段 + // 3.2.1 逾期辅助字段 var isOverdue = status is TenantBillingStatus.Overdue || (status is TenantBillingStatus.Pending && dueDate < now); var overdueDays = dueDate < now ? (int)(now - dueDate).TotalDays : 0; @@ -132,7 +132,7 @@ public sealed class GetBillingListQueryHandler( }); } - // 3.3 (空行后) 返回分页 + // 3.3 返回分页 return new PagedResult(items, page, pageSize, total); }, cancellationToken); diff --git a/src/Application/TakeoutSaaS.Application/App/Billings/Handlers/GetBillingPaymentsQueryHandler.cs b/src/Application/TakeoutSaaS.Application/App/Billings/Handlers/GetBillingPaymentsQueryHandler.cs index dcca623..b7df3b5 100644 --- a/src/Application/TakeoutSaaS.Application/App/Billings/Handlers/GetBillingPaymentsQueryHandler.cs +++ b/src/Application/TakeoutSaaS.Application/App/Billings/Handlers/GetBillingPaymentsQueryHandler.cs @@ -51,7 +51,7 @@ public sealed class GetBillingPaymentsQueryHandler( throw new BusinessException(ErrorCodes.NotFound, "账单不存在"); } - // 1.2 (空行后) 查询支付记录 + // 1.2 查询支付记录 await using var command = CreateCommand( connection, """ diff --git a/src/Application/TakeoutSaaS.Application/App/Billings/Handlers/GetBillingStatisticsQueryHandler.cs b/src/Application/TakeoutSaaS.Application/App/Billings/Handlers/GetBillingStatisticsQueryHandler.cs index 4054573..e5e3aa3 100644 --- a/src/Application/TakeoutSaaS.Application/App/Billings/Handlers/GetBillingStatisticsQueryHandler.cs +++ b/src/Application/TakeoutSaaS.Application/App/Billings/Handlers/GetBillingStatisticsQueryHandler.cs @@ -29,7 +29,7 @@ public sealed class GetBillingStatisticsQueryHandler( var endDate = request.EndDate ?? DateTime.UtcNow; var groupBy = NormalizeGroupBy(request.GroupBy); - // 2. (空行后) 查询统计数据(总览 + 趋势) + // 2. 查询统计数据(总览 + 趋势) return await dapperExecutor.QueryAsync( DatabaseConstants.AppDataSource, DatabaseConnectionRole.Read, @@ -59,7 +59,7 @@ public sealed class GetBillingStatisticsQueryHandler( var totalAmountUnpaid = summaryReader.IsDBNull(7) ? 0m : summaryReader.GetDecimal(7); var totalOverdueAmount = summaryReader.IsDBNull(8) ? 0m : summaryReader.GetDecimal(8); - // 2.2 (空行后) 趋势数据 + // 2.2 趋势数据 await using var trendCommand = CreateCommand( connection, BuildTrendSql(groupBy), @@ -83,7 +83,7 @@ public sealed class GetBillingStatisticsQueryHandler( countTrend[key] = trendReader.IsDBNull(3) ? 0 : trendReader.GetInt32(3); } - // 2.3 (空行后) 组装 DTO + // 2.3 组装 DTO return new BillingStatisticsDto { TenantId = request.TenantId, diff --git a/src/Application/TakeoutSaaS.Application/App/Billings/Handlers/GetOverdueBillingsQueryHandler.cs b/src/Application/TakeoutSaaS.Application/App/Billings/Handlers/GetOverdueBillingsQueryHandler.cs index 7acfe55..b75fecd 100644 --- a/src/Application/TakeoutSaaS.Application/App/Billings/Handlers/GetOverdueBillingsQueryHandler.cs +++ b/src/Application/TakeoutSaaS.Application/App/Billings/Handlers/GetOverdueBillingsQueryHandler.cs @@ -31,7 +31,7 @@ public sealed class GetOverdueBillingsQueryHandler( var offset = (page - 1) * pageSize; var now = DateTime.UtcNow; - // 2. (空行后) 查询总数 + 列表 + // 2. 查询总数 + 列表 return await dapperExecutor.QueryAsync( DatabaseConstants.AppDataSource, DatabaseConnectionRole.Read, @@ -46,7 +46,7 @@ public sealed class GetOverdueBillingsQueryHandler( ], token); - // 2.2 (空行后) 查询列表 + // 2.2 查询列表 await using var listCommand = CreateCommand( connection, BuildListSql(), @@ -93,7 +93,7 @@ public sealed class GetOverdueBillingsQueryHandler( }); } - // 2.3 (空行后) 返回分页 + // 2.3 返回分页 return new PagedResult(items, page, pageSize, total); }, cancellationToken); diff --git a/src/Application/TakeoutSaaS.Application/App/Billings/Handlers/RecordPaymentCommandHandler.cs b/src/Application/TakeoutSaaS.Application/App/Billings/Handlers/RecordPaymentCommandHandler.cs index ed51e97..47a81aa 100644 --- a/src/Application/TakeoutSaaS.Application/App/Billings/Handlers/RecordPaymentCommandHandler.cs +++ b/src/Application/TakeoutSaaS.Application/App/Billings/Handlers/RecordPaymentCommandHandler.cs @@ -34,7 +34,7 @@ public sealed class RecordPaymentCommandHandler( throw new BusinessException(ErrorCodes.NotFound, "账单不存在"); } - // 2. (空行后) 业务规则检查 + // 2. 业务规则检查 if (billing.Status == TenantBillingStatus.Paid) { throw new BusinessException(ErrorCodes.BusinessError, "已支付账单不允许重复收款"); @@ -45,7 +45,7 @@ public sealed class RecordPaymentCommandHandler( throw new BusinessException(ErrorCodes.BusinessError, "已取消账单不允许收款"); } - // 3. (空行后) 幂等校验:交易号唯一 + // 3. 幂等校验:交易号唯一 if (!string.IsNullOrWhiteSpace(request.TransactionNo)) { var exists = await paymentRepository.GetByTransactionNoAsync(request.TransactionNo.Trim(), cancellationToken); @@ -55,7 +55,7 @@ public sealed class RecordPaymentCommandHandler( } } - // 4. (空行后) 构建支付记录(默认待审核) + // 4. 构建支付记录(默认待审核) var now = DateTime.UtcNow; var payment = new TenantPayment { @@ -71,11 +71,11 @@ public sealed class RecordPaymentCommandHandler( Notes = request.Notes }; - // 5. (空行后) 持久化变更 + // 5. 持久化变更 await paymentRepository.AddAsync(payment, cancellationToken); await paymentRepository.SaveChangesAsync(cancellationToken); - // 6. (空行后) 返回 DTO + // 6. 返回 DTO return payment.ToPaymentRecordDto(); } } diff --git a/src/Application/TakeoutSaaS.Application/App/Billings/Handlers/UpdateBillingStatusCommandHandler.cs b/src/Application/TakeoutSaaS.Application/App/Billings/Handlers/UpdateBillingStatusCommandHandler.cs index 9ec5615..049bdd1 100644 --- a/src/Application/TakeoutSaaS.Application/App/Billings/Handlers/UpdateBillingStatusCommandHandler.cs +++ b/src/Application/TakeoutSaaS.Application/App/Billings/Handlers/UpdateBillingStatusCommandHandler.cs @@ -24,7 +24,7 @@ public sealed class UpdateBillingStatusCommandHandler( throw new BusinessException(ErrorCodes.NotFound, "账单不存在"); } - // 2. (空行后) 状态转换规则校验 + // 2. 状态转换规则校验 if (billing.Status == TenantBillingStatus.Paid && request.NewStatus != TenantBillingStatus.Paid) { throw new BusinessException(ErrorCodes.BusinessError, "已支付账单不允许改为其他状态"); @@ -35,7 +35,7 @@ public sealed class UpdateBillingStatusCommandHandler( throw new BusinessException(ErrorCodes.BusinessError, "已取消账单不允许变更状态"); } - // 3. (空行后) 更新状态与备注 + // 3. 更新状态与备注 billing.Status = request.NewStatus; if (!string.IsNullOrWhiteSpace(request.Notes)) { @@ -44,7 +44,7 @@ public sealed class UpdateBillingStatusCommandHandler( : $"{billing.Notes}\n[状态变更] {request.Notes}"; } - // 4. (空行后) 持久化 + // 4. 持久化 await billingRepository.UpdateAsync(billing, cancellationToken); await billingRepository.SaveChangesAsync(cancellationToken); diff --git a/src/Application/TakeoutSaaS.Application/App/Billings/Handlers/VerifyPaymentCommandHandler.cs b/src/Application/TakeoutSaaS.Application/App/Billings/Handlers/VerifyPaymentCommandHandler.cs index e975a94..0d58db4 100644 --- a/src/Application/TakeoutSaaS.Application/App/Billings/Handlers/VerifyPaymentCommandHandler.cs +++ b/src/Application/TakeoutSaaS.Application/App/Billings/Handlers/VerifyPaymentCommandHandler.cs @@ -26,24 +26,24 @@ public sealed class VerifyPaymentCommandHandler( throw new BusinessException(ErrorCodes.Unauthorized, "未登录或无效的操作者身份"); } - // 2. (空行后) 查询支付记录 + // 2. 查询支付记录 var payment = await paymentRepository.FindByIdAsync(request.PaymentId, cancellationToken); if (payment is null) { throw new BusinessException(ErrorCodes.NotFound, "支付记录不存在"); } - // 3. (空行后) 查询关联账单 + // 3. 查询关联账单 var billing = await billingRepository.FindByIdAsync(payment.BillingStatementId, cancellationToken); if (billing is null) { throw new BusinessException(ErrorCodes.NotFound, "关联账单不存在"); } - // 4. (空行后) 归一化审核备注 + // 4. 归一化审核备注 var normalizedNotes = string.IsNullOrWhiteSpace(request.Notes) ? null : request.Notes.Trim(); - // 5. (空行后) 根据审核结果更新支付与账单状态 + // 5. 根据审核结果更新支付与账单状态 if (request.Approved) { payment.Verify(currentUserAccessor.UserId); @@ -57,17 +57,17 @@ public sealed class VerifyPaymentCommandHandler( payment.Notes = normalizedNotes; } - // 6. (空行后) 持久化更新状态 + // 6. 持久化更新状态 await paymentRepository.UpdateAsync(payment, cancellationToken); if (request.Approved) { await billingRepository.UpdateAsync(billing, cancellationToken); } - // 7. (空行后) 保存数据库更改 + // 7. 保存数据库更改 await paymentRepository.SaveChangesAsync(cancellationToken); - // 8. (空行后) 返回 DTO + // 8. 返回 DTO return payment.ToPaymentRecordDto(); } } diff --git a/src/Application/TakeoutSaaS.Application/App/Billings/Validators/ConfirmPaymentCommandValidator.cs b/src/Application/TakeoutSaaS.Application/App/Billings/Validators/ConfirmPaymentCommandValidator.cs index a1cc4f5..d1d8f75 100644 --- a/src/Application/TakeoutSaaS.Application/App/Billings/Validators/ConfirmPaymentCommandValidator.cs +++ b/src/Application/TakeoutSaaS.Application/App/Billings/Validators/ConfirmPaymentCommandValidator.cs @@ -15,32 +15,32 @@ public sealed class ConfirmPaymentCommandValidator : AbstractValidator x.Amount) .GreaterThan(0) .WithMessage("支付金额必须大于 0") .LessThanOrEqualTo(1_000_000_000) .WithMessage("支付金额不能超过 10 亿"); - // 3. (空行后) 支付方式必填 + // 3. 支付方式必填 RuleFor(x => x.Method) .IsInEnum() .WithMessage("支付方式无效"); - // 4. (空行后) 交易号必填 + // 4. 交易号必填 RuleFor(x => x.TransactionNo) .NotEmpty() .WithMessage("交易号不能为空") .MaximumLength(64) .WithMessage("交易号不能超过 64 个字符"); - // 5. (空行后) 支付凭证 URL(可选) + // 5. 支付凭证 URL(可选) RuleFor(x => x.ProofUrl) .MaximumLength(500) .WithMessage("支付凭证 URL 不能超过 500 个字符") .When(x => !string.IsNullOrWhiteSpace(x.ProofUrl)); - // 6. (空行后) 备注(可选) + // 6. 备注(可选) RuleFor(x => x.Notes) .MaximumLength(500) .WithMessage("备注不能超过 500 个字符") diff --git a/src/Application/TakeoutSaaS.Application/App/Billings/Validators/CreateBillingCommandValidator.cs b/src/Application/TakeoutSaaS.Application/App/Billings/Validators/CreateBillingCommandValidator.cs index a992c27..14483f7 100644 --- a/src/Application/TakeoutSaaS.Application/App/Billings/Validators/CreateBillingCommandValidator.cs +++ b/src/Application/TakeoutSaaS.Application/App/Billings/Validators/CreateBillingCommandValidator.cs @@ -15,27 +15,27 @@ public sealed class CreateBillingCommandValidator : AbstractValidator x.BillingType) .IsInEnum() .WithMessage("账单类型无效"); - // 3. (空行后) 应付金额必须大于 0 + // 3. 应付金额必须大于 0 RuleFor(x => x.AmountDue) .GreaterThan(0) .WithMessage("应付金额必须大于 0"); - // 4. (空行后) 到期日必须是未来时间 + // 4. 到期日必须是未来时间 RuleFor(x => x.DueDate) .GreaterThan(DateTime.UtcNow) .WithMessage("到期日必须是未来时间"); - // 5. (空行后) 账单明细至少包含一项 + // 5. 账单明细至少包含一项 RuleFor(x => x.LineItems) .NotEmpty() .WithMessage("账单明细不能为空"); - // 6. (空行后) 账单明细项验证 + // 6. 账单明细项验证 RuleForEach(x => x.LineItems) .ChildRules(lineItem => { @@ -64,7 +64,7 @@ public sealed class CreateBillingCommandValidator : AbstractValidator x.Notes) .MaximumLength(500) .WithMessage("备注不能超过 500 个字符") diff --git a/src/Application/TakeoutSaaS.Application/App/Billings/Validators/RecordPaymentCommandValidator.cs b/src/Application/TakeoutSaaS.Application/App/Billings/Validators/RecordPaymentCommandValidator.cs index 8af9a36..266eb1e 100644 --- a/src/Application/TakeoutSaaS.Application/App/Billings/Validators/RecordPaymentCommandValidator.cs +++ b/src/Application/TakeoutSaaS.Application/App/Billings/Validators/RecordPaymentCommandValidator.cs @@ -15,32 +15,32 @@ public sealed class RecordPaymentCommandValidator : AbstractValidator x.Amount) .GreaterThan(0) .WithMessage("支付金额必须大于 0") .LessThanOrEqualTo(1_000_000_000) .WithMessage("支付金额不能超过 10 亿"); - // 3. (空行后) 支付方式必填 + // 3. 支付方式必填 RuleFor(x => x.Method) .IsInEnum() .WithMessage("支付方式无效"); - // 4. (空行后) 交易号必填 + // 4. 交易号必填 RuleFor(x => x.TransactionNo) .NotEmpty() .WithMessage("交易号不能为空") .MaximumLength(64) .WithMessage("交易号不能超过 64 个字符"); - // 5. (空行后) 支付凭证 URL(可选) + // 5. 支付凭证 URL(可选) RuleFor(x => x.ProofUrl) .MaximumLength(500) .WithMessage("支付凭证 URL 不能超过 500 个字符") .When(x => !string.IsNullOrWhiteSpace(x.ProofUrl)); - // 6. (空行后) 备注(可选) + // 6. 备注(可选) RuleFor(x => x.Notes) .MaximumLength(500) .WithMessage("备注不能超过 500 个字符") diff --git a/src/Application/TakeoutSaaS.Application/App/Billings/Validators/UpdateBillingStatusCommandValidator.cs b/src/Application/TakeoutSaaS.Application/App/Billings/Validators/UpdateBillingStatusCommandValidator.cs index cfa5401..025b6ed 100644 --- a/src/Application/TakeoutSaaS.Application/App/Billings/Validators/UpdateBillingStatusCommandValidator.cs +++ b/src/Application/TakeoutSaaS.Application/App/Billings/Validators/UpdateBillingStatusCommandValidator.cs @@ -15,12 +15,12 @@ public sealed class UpdateBillingStatusCommandValidator : AbstractValidator x.NewStatus) .IsInEnum() .WithMessage("新状态无效"); - // 3. (空行后) 备注长度限制(可选) + // 3. 备注长度限制(可选) RuleFor(x => x.Notes) .MaximumLength(500) .WithMessage("备注不能超过 500 个字符") diff --git a/src/Application/TakeoutSaaS.Application/App/QuotaPackages/Handlers/PurchaseQuotaPackageCommandHandler.cs b/src/Application/TakeoutSaaS.Application/App/QuotaPackages/Handlers/PurchaseQuotaPackageCommandHandler.cs index 5c90af9..65d7a68 100644 --- a/src/Application/TakeoutSaaS.Application/App/QuotaPackages/Handlers/PurchaseQuotaPackageCommandHandler.cs +++ b/src/Application/TakeoutSaaS.Application/App/QuotaPackages/Handlers/PurchaseQuotaPackageCommandHandler.cs @@ -54,7 +54,7 @@ public sealed class PurchaseQuotaPackageCommandHandler( quotaUsage.LimitValue += quotaPackage.QuotaValue; await quotaPackageRepository.UpdateUsageAsync(quotaUsage, cancellationToken); - // 4.1 (空行后) 记录配额变更历史(购买配额包视为“剩余增加”) + // 4.1 记录配额变更历史(购买配额包视为“剩余增加”) await quotaUsageHistoryRepository.AddAsync(new TenantQuotaUsageHistory { TenantId = request.TenantId, diff --git a/src/Application/TakeoutSaaS.Application/App/Subscriptions/Handlers/ProcessAutoRenewalCommandHandler.cs b/src/Application/TakeoutSaaS.Application/App/Subscriptions/Handlers/ProcessAutoRenewalCommandHandler.cs index 80d8048..64548ea 100644 --- a/src/Application/TakeoutSaaS.Application/App/Subscriptions/Handlers/ProcessAutoRenewalCommandHandler.cs +++ b/src/Application/TakeoutSaaS.Application/App/Subscriptions/Handlers/ProcessAutoRenewalCommandHandler.cs @@ -120,7 +120,7 @@ public sealed class ProcessAutoRenewalCommandHandler( // 1. 以年月差作为周期(月),兼容“按月续费”模型 var months = (effectiveTo.Year - effectiveFrom.Year) * 12 + effectiveTo.Month - effectiveFrom.Month; - // 2. (空行后) 对不足 1 个月的情况兜底为 1 + // 2. 对不足 1 个月的情况兜底为 1 return months <= 0 ? 1 : months; } @@ -134,7 +134,7 @@ public sealed class ProcessAutoRenewalCommandHandler( return monthlyPrice * durationMonths; } - // 2. (空行后) 按年 + 月组合计算金额 + // 2. 按年 + 月组合计算金额 var years = durationMonths / 12; var remainingMonths = durationMonths % 12; return yearlyPrice.Value * years + monthlyPrice * remainingMonths; diff --git a/src/Application/TakeoutSaaS.Application/App/Subscriptions/Handlers/ProcessSubscriptionExpiryCommandHandler.cs b/src/Application/TakeoutSaaS.Application/App/Subscriptions/Handlers/ProcessSubscriptionExpiryCommandHandler.cs index bc55111..0d798bb 100644 --- a/src/Application/TakeoutSaaS.Application/App/Subscriptions/Handlers/ProcessSubscriptionExpiryCommandHandler.cs +++ b/src/Application/TakeoutSaaS.Application/App/Subscriptions/Handlers/ProcessSubscriptionExpiryCommandHandler.cs @@ -40,14 +40,14 @@ public sealed class ProcessSubscriptionExpiryCommandHandler( await subscriptionRepository.UpdateAsync(subscription, cancellationToken); } - // 3. (空行后) 宽限期到期自动暂停 + // 3. 宽限期到期自动暂停 foreach (var subscription in gracePeriodExpired) { subscription.Status = SubscriptionStatus.Suspended; await subscriptionRepository.UpdateAsync(subscription, cancellationToken); } - // 4. (空行后) 保存变更 + // 4. 保存变更 var totalChanged = expiredActive.Count + gracePeriodExpired.Count; if (totalChanged > 0) { diff --git a/src/Application/TakeoutSaaS.Application/App/Tenants/Handlers/CheckTenantQuotaCommandHandler.cs b/src/Application/TakeoutSaaS.Application/App/Tenants/Handlers/CheckTenantQuotaCommandHandler.cs index 7ea5892..496d331 100644 --- a/src/Application/TakeoutSaaS.Application/App/Tenants/Handlers/CheckTenantQuotaCommandHandler.cs +++ b/src/Application/TakeoutSaaS.Application/App/Tenants/Handlers/CheckTenantQuotaCommandHandler.cs @@ -63,7 +63,7 @@ public sealed class CheckTenantQuotaCommandHandler( ResetCycle = ResolveResetCycle(request.QuotaType) }; - // 4.1 (空行后) 记录是否为首次初始化(用于落库历史) + // 4.1 记录是否为首次初始化(用于落库历史) var isNewUsage = usage.Id == 0; var usedAfter = usage.UsedValue + request.Delta; @@ -79,7 +79,7 @@ public sealed class CheckTenantQuotaCommandHandler( usage.UsedValue = usedAfter; usage.ResetCycle ??= ResolveResetCycle(request.QuotaType); - // 5.1 (空行后) 落库历史(初始化 + 本次消耗) + // 5.1 落库历史(初始化 + 本次消耗) var now = DateTime.UtcNow; if (isNewUsage) { diff --git a/src/Application/TakeoutSaaS.Application/App/Tenants/Handlers/ClaimTenantReviewCommandHandler.cs b/src/Application/TakeoutSaaS.Application/App/Tenants/Handlers/ClaimTenantReviewCommandHandler.cs index 277d8f4..705a32f 100644 --- a/src/Application/TakeoutSaaS.Application/App/Tenants/Handlers/ClaimTenantReviewCommandHandler.cs +++ b/src/Application/TakeoutSaaS.Application/App/Tenants/Handlers/ClaimTenantReviewCommandHandler.cs @@ -39,13 +39,13 @@ public sealed class ClaimTenantReviewCommandHandler( throw new BusinessException(ErrorCodes.Conflict, $"该审核已被 {existingClaim.ClaimedByName} 领取"); } - // 3. (空行后) 获取当前用户显示名(用于展示快照) + // 3. 获取当前用户显示名(用于展示快照) var profile = await adminAuthService.GetProfileAsync(currentUserAccessor.UserId, cancellationToken); var displayName = string.IsNullOrWhiteSpace(profile.DisplayName) ? $"user:{currentUserAccessor.UserId}" : profile.DisplayName; - // 4. (空行后) 构造领取记录与审计日志 + // 4. 构造领取记录与审计日志 var now = DateTime.UtcNow; var claim = new TenantReviewClaim { @@ -68,7 +68,7 @@ public sealed class ClaimTenantReviewCommandHandler( CurrentStatus = tenant.Status }; - // 5. (空行后) 写入领取记录(处理并发领取冲突) + // 5. 写入领取记录(处理并发领取冲突) var success = await tenantRepository.TryAddReviewClaimAsync(claim, auditLog, cancellationToken); if (!success) { @@ -86,7 +86,7 @@ public sealed class ClaimTenantReviewCommandHandler( throw new BusinessException(ErrorCodes.Conflict, $"该审核已被 {current.ClaimedByName} 领取"); } - // 6. (空行后) 返回领取结果 + // 6. 返回领取结果 return claim.ToDto(); } } diff --git a/src/Application/TakeoutSaaS.Application/App/Tenants/Handlers/CreateTenantAdminResetLinkTokenCommandHandler.cs b/src/Application/TakeoutSaaS.Application/App/Tenants/Handlers/CreateTenantAdminResetLinkTokenCommandHandler.cs index ed3ea01..788dfa3 100644 --- a/src/Application/TakeoutSaaS.Application/App/Tenants/Handlers/CreateTenantAdminResetLinkTokenCommandHandler.cs +++ b/src/Application/TakeoutSaaS.Application/App/Tenants/Handlers/CreateTenantAdminResetLinkTokenCommandHandler.cs @@ -37,11 +37,11 @@ public sealed class CreateTenantAdminResetLinkTokenCommandHandler( throw new BusinessException(ErrorCodes.Forbidden, "仅平台超级管理员可生成重置链接"); } - // 2. (空行后) 校验租户存在且存在主管理员 + // 2. 校验租户存在且存在主管理员 var tenant = await tenantRepository.FindByIdAsync(request.TenantId, cancellationToken) ?? throw new BusinessException(ErrorCodes.NotFound, "租户不存在"); - // 2.1 (空行后) 若缺少主管理员则自动回填(兼容历史数据) + // 2.1 若缺少主管理员则自动回填(兼容历史数据) if (!tenant.PrimaryOwnerUserId.HasValue || tenant.PrimaryOwnerUserId.Value == 0) { var originalContextForFix = tenantContextAccessor.Current; @@ -65,10 +65,10 @@ public sealed class CreateTenantAdminResetLinkTokenCommandHandler( } } - // 3. (空行后) 签发一次性重置令牌(默认 24 小时有效) + // 3. 签发一次性重置令牌(默认 24 小时有效) var token = await tokenStore.IssueAsync(tenant.PrimaryOwnerUserId.Value, DateTime.UtcNow.AddHours(24), cancellationToken); - // 4. (空行后) 写入审计日志 + // 4. 写入审计日志 var operatorProfile = await adminAuthService.GetProfileAsync(currentUserAccessor.UserId, cancellationToken); var operatorName = string.IsNullOrWhiteSpace(operatorProfile.DisplayName) ? $"user:{currentUserAccessor.UserId}" @@ -88,7 +88,7 @@ public sealed class CreateTenantAdminResetLinkTokenCommandHandler( await tenantRepository.AddAuditLogAsync(auditLog, cancellationToken); await tenantRepository.SaveChangesAsync(cancellationToken); - // 5. (空行后) 返回令牌 + // 5. 返回令牌 return token; } } diff --git a/src/Application/TakeoutSaaS.Application/App/Tenants/Handlers/CreateTenantManuallyCommandHandler.cs b/src/Application/TakeoutSaaS.Application/App/Tenants/Handlers/CreateTenantManuallyCommandHandler.cs index 15d5451..8297b18 100644 --- a/src/Application/TakeoutSaaS.Application/App/Tenants/Handlers/CreateTenantManuallyCommandHandler.cs +++ b/src/Application/TakeoutSaaS.Application/App/Tenants/Handlers/CreateTenantManuallyCommandHandler.cs @@ -49,7 +49,7 @@ public sealed class CreateTenantManuallyCommandHandler( throw new BusinessException(ErrorCodes.Conflict, $"租户编码 {normalizedCode} 已存在"); } - // 3. (空行后) 校验联系人手机号唯一性(仅当填写时) + // 3. 校验联系人手机号唯一性(仅当填写时) if (!string.IsNullOrWhiteSpace(request.ContactPhone)) { var normalizedPhone = request.ContactPhone.Trim(); @@ -59,14 +59,14 @@ public sealed class CreateTenantManuallyCommandHandler( } } - // 4. (空行后) 校验管理员账号唯一性 + // 4. 校验管理员账号唯一性 var normalizedAccount = request.AdminAccount.Trim(); if (await identityUserRepository.ExistsByAccountAsync(normalizedAccount, cancellationToken)) { throw new BusinessException(ErrorCodes.Conflict, $"账号 {normalizedAccount} 已存在"); } - // 5. (空行后) 校验套餐存在且可用 + // 5. 校验套餐存在且可用 var package = await tenantPackageRepository.FindByIdAsync(request.TenantPackageId, cancellationToken) ?? throw new BusinessException(ErrorCodes.NotFound, "套餐不存在"); if (!package.IsActive) @@ -74,12 +74,12 @@ public sealed class CreateTenantManuallyCommandHandler( throw new BusinessException(ErrorCodes.BadRequest, "套餐未启用,无法绑定订阅"); } - // 6. (空行后) 计算订阅生效与到期时间(UTC) + // 6. 计算订阅生效与到期时间(UTC) var now = DateTime.UtcNow; var subscriptionEffectiveFrom = request.SubscriptionEffectiveFrom ?? now; var subscriptionEffectiveTo = subscriptionEffectiveFrom.AddMonths(request.DurationMonths); - // 7. (空行后) 构建租户与订阅 + // 7. 构建租户与订阅 var tenantId = idGenerator.NextId(); var tenant = new Tenant { @@ -108,7 +108,7 @@ public sealed class CreateTenantManuallyCommandHandler( Remarks = request.Remarks }; - // 8. (空行后) 构建订阅实体 + // 8. 构建订阅实体 var subscription = new TenantSubscription { Id = idGenerator.NextId(), @@ -123,7 +123,7 @@ public sealed class CreateTenantManuallyCommandHandler( Notes = request.SubscriptionNotes }; - // 9. (空行后) 构建认证资料(默认直接通过) + // 9. 构建认证资料(默认直接通过) var actorName = currentUserAccessor.IsAuthenticated ? $"user:{currentUserAccessor.UserId}" : "system"; @@ -150,7 +150,7 @@ public sealed class CreateTenantManuallyCommandHandler( ReviewRemarks = request.ReviewRemarks }; - // 10. (空行后) 写入审计日志与订阅历史 + // 10. 写入审计日志与订阅历史 await tenantRepository.AddAuditLogAsync(new TenantAuditLog { TenantId = tenantId, @@ -201,13 +201,13 @@ public sealed class CreateTenantManuallyCommandHandler( Notes = request.SubscriptionNotes }, cancellationToken); - // 11. (空行后) 持久化租户、订阅与认证资料 + // 11. 持久化租户、订阅与认证资料 await tenantRepository.AddTenantAsync(tenant, cancellationToken); await tenantRepository.AddSubscriptionAsync(subscription, cancellationToken); await tenantRepository.UpsertVerificationProfileAsync(verification, cancellationToken); await tenantRepository.SaveChangesAsync(cancellationToken); - // 12. (空行后) 临时切换租户上下文,保证身份与权限写入正确 + // 12. 临时切换租户上下文,保证身份与权限写入正确 var previousContext = tenantContextAccessor.Current; tenantContextAccessor.Current = new TenantContext(tenant.Id, tenant.Code, "manual-create"); try @@ -228,7 +228,7 @@ public sealed class CreateTenantManuallyCommandHandler( await identityUserRepository.AddAsync(adminUser, cancellationToken); await identityUserRepository.SaveChangesAsync(cancellationToken); - // 14. (空行后) 初始化租户管理员角色模板并绑定角色 + // 14. 初始化租户管理员角色模板并绑定角色 await mediator.Send(new InitializeRoleTemplatesCommand { TemplateCodes = new[] { "tenant-admin" } @@ -244,7 +244,7 @@ public sealed class CreateTenantManuallyCommandHandler( }, cancellationToken); } - // 15. (空行后) 回写租户所有者账号 + // 15. 回写租户所有者账号 tenant.PrimaryOwnerUserId = adminUser.Id; await tenantRepository.UpdateTenantAsync(tenant, cancellationToken); await tenantRepository.SaveChangesAsync(cancellationToken); @@ -255,7 +255,7 @@ public sealed class CreateTenantManuallyCommandHandler( tenantContextAccessor.Current = previousContext; } - // 17. (空行后) 返回创建结果 + // 17. 返回创建结果 logger.LogInformation("已后台手动创建租户 {TenantCode}", tenant.Code); return new TenantDetailDto diff --git a/src/Application/TakeoutSaaS.Application/App/Tenants/Handlers/ExtendTenantSubscriptionCommandHandler.cs b/src/Application/TakeoutSaaS.Application/App/Tenants/Handlers/ExtendTenantSubscriptionCommandHandler.cs index 965cd77..0bcb1d7 100644 --- a/src/Application/TakeoutSaaS.Application/App/Tenants/Handlers/ExtendTenantSubscriptionCommandHandler.cs +++ b/src/Application/TakeoutSaaS.Application/App/Tenants/Handlers/ExtendTenantSubscriptionCommandHandler.cs @@ -45,7 +45,7 @@ public sealed class ExtendTenantSubscriptionCommandHandler( ? $"user:{currentUserAccessor.UserId}" : "system"; - // 3. (空行后) 创建续费订阅 + // 3. 创建续费订阅 var subscription = new TenantSubscription { Id = idGenerator.NextId(), @@ -75,7 +75,7 @@ public sealed class ExtendTenantSubscriptionCommandHandler( Notes = request.Notes }, cancellationToken); - // 4. (空行后) 若租户处于到期状态则恢复为正常(冻结状态需先解冻) + // 4. 若租户处于到期状态则恢复为正常(冻结状态需先解冻) if (tenant.Status == TenantStatus.Expired) { tenant.Status = TenantStatus.Active; @@ -86,7 +86,7 @@ public sealed class ExtendTenantSubscriptionCommandHandler( await tenantRepository.UpdateTenantAsync(tenant, cancellationToken); - // 5. (空行后) 记录审计 + // 5. 记录审计 await tenantRepository.AddAuditLogAsync(new TenantAuditLog { TenantId = tenant.Id, diff --git a/src/Application/TakeoutSaaS.Application/App/Tenants/Handlers/ForceClaimTenantReviewCommandHandler.cs b/src/Application/TakeoutSaaS.Application/App/Tenants/Handlers/ForceClaimTenantReviewCommandHandler.cs index b03d107..90e9cfe 100644 --- a/src/Application/TakeoutSaaS.Application/App/Tenants/Handlers/ForceClaimTenantReviewCommandHandler.cs +++ b/src/Application/TakeoutSaaS.Application/App/Tenants/Handlers/ForceClaimTenantReviewCommandHandler.cs @@ -33,7 +33,7 @@ public sealed class ForceClaimTenantReviewCommandHandler( ? $"user:{currentUserAccessor.UserId}" : profile.DisplayName; - // 3. (空行后) 读取当前领取记录(可跟踪用于更新) + // 3. 读取当前领取记录(可跟踪用于更新) var claim = await tenantRepository.FindActiveReviewClaimAsync(request.TenantId, cancellationToken); if (claim == null) { @@ -75,13 +75,13 @@ public sealed class ForceClaimTenantReviewCommandHandler( return created.ToDto(); } - // 5. (空行后) 已由自己领取则直接返回 + // 5. 已由自己领取则直接返回 if (claim.ClaimedBy == currentUserAccessor.UserId) { return claim.ToDto(); } - // 6. (空行后) 更新领取人并记录审计 + // 6. 更新领取人并记录审计 var previousOwner = claim.ClaimedByName; claim.ClaimedBy = currentUserAccessor.UserId; claim.ClaimedByName = displayName; diff --git a/src/Application/TakeoutSaaS.Application/App/Tenants/Handlers/FreezeTenantCommandHandler.cs b/src/Application/TakeoutSaaS.Application/App/Tenants/Handlers/FreezeTenantCommandHandler.cs index 0435f8e..b48bbae 100644 --- a/src/Application/TakeoutSaaS.Application/App/Tenants/Handlers/FreezeTenantCommandHandler.cs +++ b/src/Application/TakeoutSaaS.Application/App/Tenants/Handlers/FreezeTenantCommandHandler.cs @@ -44,7 +44,7 @@ public sealed class FreezeTenantCommandHandler( tenant.SuspendedAt = DateTime.UtcNow; tenant.SuspensionReason = request.Reason; - // 3. (空行后) 同步暂停订阅 + // 3. 同步暂停订阅 if (subscription != null) { subscription.Status = SubscriptionStatus.Suspended; @@ -53,7 +53,7 @@ public sealed class FreezeTenantCommandHandler( await tenantRepository.UpdateTenantAsync(tenant, cancellationToken); - // 4. (空行后) 记录审计 + // 4. 记录审计 var actorName = currentUserAccessor.IsAuthenticated ? $"user:{currentUserAccessor.UserId}" : "system"; diff --git a/src/Application/TakeoutSaaS.Application/App/Tenants/Handlers/GetTenantByIdQueryHandler.cs b/src/Application/TakeoutSaaS.Application/App/Tenants/Handlers/GetTenantByIdQueryHandler.cs index 694158f..7ae6f83 100644 --- a/src/Application/TakeoutSaaS.Application/App/Tenants/Handlers/GetTenantByIdQueryHandler.cs +++ b/src/Application/TakeoutSaaS.Application/App/Tenants/Handlers/GetTenantByIdQueryHandler.cs @@ -26,12 +26,12 @@ public sealed class GetTenantByIdQueryHandler( var subscription = await tenantRepository.GetActiveSubscriptionAsync(request.TenantId, cancellationToken); var verification = await tenantRepository.GetVerificationProfileAsync(request.TenantId, cancellationToken); - // 3. (空行后) 查询当前套餐 + // 3. 查询当前套餐 var package = subscription == null ? null : await tenantPackageRepository.FindByIdAsync(subscription.TenantPackageId, cancellationToken); - // 4. (空行后) 组装返回 + // 4. 组装返回 return new TenantDetailDto { Tenant = TenantMapping.ToDto(tenant, subscription, verification), diff --git a/src/Application/TakeoutSaaS.Application/App/Tenants/Handlers/GetTenantPackageTenantsQueryHandler.cs b/src/Application/TakeoutSaaS.Application/App/Tenants/Handlers/GetTenantPackageTenantsQueryHandler.cs index 997a0ed..f85a5b7 100644 --- a/src/Application/TakeoutSaaS.Application/App/Tenants/Handlers/GetTenantPackageTenantsQueryHandler.cs +++ b/src/Application/TakeoutSaaS.Application/App/Tenants/Handlers/GetTenantPackageTenantsQueryHandler.cs @@ -24,13 +24,13 @@ public sealed class GetTenantPackageTenantsQueryHandler(IDapperExecutor dapperEx var pageSize = request.PageSize <= 0 ? 20 : request.PageSize; var keyword = string.IsNullOrWhiteSpace(request.Keyword) ? null : request.Keyword.Trim(); - // 2. (空行后) 以当前时间为准筛选“有效订阅” + // 2. 以当前时间为准筛选“有效订阅” var now = DateTime.UtcNow; var expiringDays = request.ExpiringWithinDays is > 0 ? request.ExpiringWithinDays : null; var expiryEnd = expiringDays.HasValue ? now.AddDays(expiringDays.Value) : (DateTime?)null; var offset = (page - 1) * pageSize; - // 3. (空行后) 查询总数 + 列表 + // 3. 查询总数 + 列表 return await dapperExecutor.QueryAsync( DatabaseConstants.AppDataSource, DatabaseConnectionRole.Read, @@ -48,7 +48,7 @@ public sealed class GetTenantPackageTenantsQueryHandler(IDapperExecutor dapperEx ], token); - // 3.2 (空行后) 查询列表 + // 3.2 查询列表 var listSql = BuildListSql(expiryEnd.HasValue); await using var listCommand = CreateCommand( connection, @@ -79,7 +79,7 @@ public sealed class GetTenantPackageTenantsQueryHandler(IDapperExecutor dapperEx }); } - // 3.3 (空行后) 返回分页 + // 3.3 返回分页 return new PagedResult(items, page, pageSize, total); }, cancellationToken); diff --git a/src/Application/TakeoutSaaS.Application/App/Tenants/Handlers/GetTenantPackageUsagesQueryHandler.cs b/src/Application/TakeoutSaaS.Application/App/Tenants/Handlers/GetTenantPackageUsagesQueryHandler.cs index cf91d7f..b5e5f18 100644 --- a/src/Application/TakeoutSaaS.Application/App/Tenants/Handlers/GetTenantPackageUsagesQueryHandler.cs +++ b/src/Application/TakeoutSaaS.Application/App/Tenants/Handlers/GetTenantPackageUsagesQueryHandler.cs @@ -23,14 +23,14 @@ public sealed class GetTenantPackageUsagesQueryHandler(IDapperExecutor dapperExe .Distinct() .ToArray(); - // 2. (空行后) 构造 SQL(以当前时间为准统计“有效订阅/使用租户/到期分布”) + // 2. 构造 SQL(以当前时间为准统计“有效订阅/使用租户/到期分布”) var now = DateTime.UtcNow; var date7 = now.AddDays(7); var date15 = now.AddDays(15); var date30 = now.AddDays(30); var sql = BuildSql(ids, out var parameters, now, date7, date15, date30); - // 3. (空行后) 查询统计结果 + // 3. 查询统计结果 return await dapperExecutor.QueryAsync( DatabaseConstants.AppDataSource, DatabaseConnectionRole.Read, @@ -40,7 +40,7 @@ public sealed class GetTenantPackageUsagesQueryHandler(IDapperExecutor dapperExe await using var reader = await command.ExecuteReaderAsync(token); var list = new List(); - // 4. (空行后) 逐行读取 + // 4. 逐行读取 while (await reader.ReadAsync(token)) { list.Add(new TenantPackageUsageDto @@ -94,7 +94,7 @@ public sealed class GetTenantPackageUsagesQueryHandler(IDapperExecutor dapperExe ("date30", date30) }; - // 2. (空行后) 可选按套餐 ID 过滤 + // 2. 可选按套餐 ID 过滤 if (ids is { Length: > 0 }) { builder.Append(" and \"TenantPackageId\" in ("); @@ -113,7 +113,7 @@ public sealed class GetTenantPackageUsagesQueryHandler(IDapperExecutor dapperExe builder.AppendLine(")"); } - // 3. (空行后) 分组与回连套餐表 + // 3. 分组与回连套餐表 builder.AppendLine(""" group by "TenantPackageId" ) diff --git a/src/Application/TakeoutSaaS.Application/App/Tenants/Handlers/GetTenantQuotaUsageHistoryQueryHandler.cs b/src/Application/TakeoutSaaS.Application/App/Tenants/Handlers/GetTenantQuotaUsageHistoryQueryHandler.cs index 6494852..57ecd65 100644 --- a/src/Application/TakeoutSaaS.Application/App/Tenants/Handlers/GetTenantQuotaUsageHistoryQueryHandler.cs +++ b/src/Application/TakeoutSaaS.Application/App/Tenants/Handlers/GetTenantQuotaUsageHistoryQueryHandler.cs @@ -27,12 +27,12 @@ public sealed class GetTenantQuotaUsageHistoryQueryHandler( _ = await tenantRepository.FindByIdAsync(request.TenantId, cancellationToken) ?? throw new BusinessException(ErrorCodes.NotFound, "租户不存在"); - // 2. (空行后) 规范化分页 + // 2. 规范化分页 var page = request.Page <= 0 ? 1 : request.Page; var pageSize = request.PageSize is <= 0 or > 100 ? 10 : request.PageSize; var offset = (page - 1) * pageSize; - // 3. (空行后) 查询总数 + 列表 + // 3. 查询总数 + 列表 return await dapperExecutor.QueryAsync( DatabaseConstants.AppDataSource, DatabaseConnectionRole.Read, @@ -50,7 +50,7 @@ public sealed class GetTenantQuotaUsageHistoryQueryHandler( ], token); - // 3.2 (空行后) 查询列表 + // 3.2 查询列表 await using var listCommand = CreateCommand( connection, BuildListSql(), @@ -75,7 +75,7 @@ public sealed class GetTenantQuotaUsageHistoryQueryHandler( decimal? changeAmount = reader.IsDBNull(5) ? null : reader.GetDecimal(5); var changeReason = reader.IsDBNull(6) ? null : reader.GetString(6); - // 3.2.1 (空行后) 映射 DTO + // 3.2.1 映射 DTO items.Add(new QuotaUsageHistoryDto { QuotaType = quotaType, @@ -88,7 +88,7 @@ public sealed class GetTenantQuotaUsageHistoryQueryHandler( }); } - // 3.3 (空行后) 返回分页 + // 3.3 返回分页 return new PagedResult(items, page, pageSize, total); }, cancellationToken); diff --git a/src/Application/TakeoutSaaS.Application/App/Tenants/Handlers/ImpersonateTenantCommandHandler.cs b/src/Application/TakeoutSaaS.Application/App/Tenants/Handlers/ImpersonateTenantCommandHandler.cs index 5df8441..55c60ca 100644 --- a/src/Application/TakeoutSaaS.Application/App/Tenants/Handlers/ImpersonateTenantCommandHandler.cs +++ b/src/Application/TakeoutSaaS.Application/App/Tenants/Handlers/ImpersonateTenantCommandHandler.cs @@ -38,17 +38,17 @@ public sealed class ImpersonateTenantCommandHandler( throw new BusinessException(ErrorCodes.Forbidden, "仅平台超级管理员可执行伪装登录"); } - // 2. (空行后) 读取操作者信息(在平台租户上下文内) + // 2. 读取操作者信息(在平台租户上下文内) var operatorProfile = await adminAuthService.GetProfileAsync(currentUserAccessor.UserId, cancellationToken); var operatorName = string.IsNullOrWhiteSpace(operatorProfile.DisplayName) ? $"user:{currentUserAccessor.UserId}" : operatorProfile.DisplayName; - // 2. (空行后) 校验租户存在且存在主管理员 + // 2. 校验租户存在且存在主管理员 var tenant = await tenantRepository.FindByIdAsync(request.TenantId, cancellationToken) ?? throw new BusinessException(ErrorCodes.NotFound, "租户不存在"); - // 2.1 (空行后) 若缺少主管理员则自动回填(兼容历史数据) + // 2.1 若缺少主管理员则自动回填(兼容历史数据) if (!tenant.PrimaryOwnerUserId.HasValue || tenant.PrimaryOwnerUserId.Value == 0) { var originalContextForFix = tenantContextAccessor.Current; @@ -72,7 +72,7 @@ public sealed class ImpersonateTenantCommandHandler( } } - // 3. (空行后) 进入目标租户上下文以读取租户内用户(避免多租户查询过滤导致找不到用户) + // 3. 进入目标租户上下文以读取租户内用户(避免多租户查询过滤导致找不到用户) var originalTenantContext = tenantContextAccessor.Current; tenantContextAccessor.Current = new TenantContext(tenant.Id, null, "admin:impersonate"); try @@ -81,7 +81,7 @@ public sealed class ImpersonateTenantCommandHandler( var targetProfile = await adminAuthService.GetProfileAsync(tenant.PrimaryOwnerUserId.Value, cancellationToken); var token = await jwtTokenService.CreateTokensAsync(targetProfile, false, cancellationToken); - // 5. (空行后) 恢复租户上下文后写入审计日志 + // 5. 恢复租户上下文后写入审计日志 tenantContextAccessor.Current = originalTenantContext; var auditLog = new TenantAuditLog { @@ -97,12 +97,12 @@ public sealed class ImpersonateTenantCommandHandler( await tenantRepository.AddAuditLogAsync(auditLog, cancellationToken); await tenantRepository.SaveChangesAsync(cancellationToken); - // 6. (空行后) 返回令牌 + // 6. 返回令牌 return token; } finally { - // 7. (空行后) 确保恢复租户上下文 + // 7. 确保恢复租户上下文 tenantContextAccessor.Current = originalTenantContext; } } diff --git a/src/Application/TakeoutSaaS.Application/App/Tenants/Handlers/ReleaseTenantReviewClaimCommandHandler.cs b/src/Application/TakeoutSaaS.Application/App/Tenants/Handlers/ReleaseTenantReviewClaimCommandHandler.cs index bec8f6d..7124f01 100644 --- a/src/Application/TakeoutSaaS.Application/App/Tenants/Handlers/ReleaseTenantReviewClaimCommandHandler.cs +++ b/src/Application/TakeoutSaaS.Application/App/Tenants/Handlers/ReleaseTenantReviewClaimCommandHandler.cs @@ -34,13 +34,13 @@ public sealed class ReleaseTenantReviewClaimCommandHandler( return null; } - // 3. (空行后) 非领取人不允许释放(如需接管请使用强制接管) + // 3. 非领取人不允许释放(如需接管请使用强制接管) if (claim.ClaimedBy != currentUserAccessor.UserId) { throw new BusinessException(ErrorCodes.Conflict, $"该审核已被 {claim.ClaimedByName} 领取"); } - // 4. (空行后) 释放领取并记录审计 + // 4. 释放领取并记录审计 var profile = await adminAuthService.GetProfileAsync(currentUserAccessor.UserId, cancellationToken); var displayName = string.IsNullOrWhiteSpace(profile.DisplayName) ? $"user:{currentUserAccessor.UserId}" diff --git a/src/Application/TakeoutSaaS.Application/App/Tenants/Handlers/ReviewTenantCommandHandler.cs b/src/Application/TakeoutSaaS.Application/App/Tenants/Handlers/ReviewTenantCommandHandler.cs index 29990e7..7909b76 100644 --- a/src/Application/TakeoutSaaS.Application/App/Tenants/Handlers/ReviewTenantCommandHandler.cs +++ b/src/Application/TakeoutSaaS.Application/App/Tenants/Handlers/ReviewTenantCommandHandler.cs @@ -202,7 +202,7 @@ public sealed class ReviewTenantCommandHandler( CurrentStatus = tenant.Status }, cancellationToken); - // 7. (空行后) 审核完成自动释放领取 + // 7. 审核完成自动释放领取 reviewClaim.ReleasedAt = DateTime.UtcNow; await tenantRepository.UpdateReviewClaimAsync(reviewClaim, cancellationToken); await tenantRepository.AddAuditLogAsync(new Domain.Tenants.Entities.TenantAuditLog diff --git a/src/Application/TakeoutSaaS.Application/App/Tenants/Handlers/SearchTenantsQueryHandler.cs b/src/Application/TakeoutSaaS.Application/App/Tenants/Handlers/SearchTenantsQueryHandler.cs index 66bc459..cba1dfe 100644 --- a/src/Application/TakeoutSaaS.Application/App/Tenants/Handlers/SearchTenantsQueryHandler.cs +++ b/src/Application/TakeoutSaaS.Application/App/Tenants/Handlers/SearchTenantsQueryHandler.cs @@ -27,24 +27,24 @@ public sealed class SearchTenantsQueryHandler(ITenantRepository tenantRepository request.PageSize, cancellationToken); - // 2. (空行后) 无数据直接返回 + // 2. 无数据直接返回 if (tenants.Count == 0) { return new PagedResult([], request.Page, request.PageSize, total); } - // 3. (空行后) 批量查询订阅与实名资料(避免 N+1) + // 3. 批量查询订阅与实名资料(避免 N+1) var tenantIds = tenants.Select(x => x.Id).ToArray(); var subscriptions = await tenantRepository.GetSubscriptionsAsync(tenantIds, cancellationToken); var verifications = await tenantRepository.GetVerificationProfilesAsync(tenantIds, cancellationToken); - // 4. (空行后) 构建订阅与实名资料映射 + // 4. 构建订阅与实名资料映射 var subscriptionByTenantId = subscriptions .GroupBy(x => x.TenantId) .ToDictionary(x => x.Key, x => x.FirstOrDefault()); var verificationByTenantId = verifications.ToDictionary(x => x.TenantId); - // 5. (空行后) 映射 DTO(带订阅与认证) + // 5. 映射 DTO(带订阅与认证) var result = new List(tenants.Count); foreach (var tenant in tenants) { @@ -53,7 +53,7 @@ public sealed class SearchTenantsQueryHandler(ITenantRepository tenantRepository result.Add(TenantMapping.ToDto(tenant, subscription, verification)); } - // 6. (空行后) 返回分页结果 + // 6. 返回分页结果 return new PagedResult(result, request.Page, request.PageSize, total); } } diff --git a/src/Application/TakeoutSaaS.Application/App/Tenants/Handlers/SelfRegisterTenantCommandHandler.cs b/src/Application/TakeoutSaaS.Application/App/Tenants/Handlers/SelfRegisterTenantCommandHandler.cs index 2b045a4..4d15ddf 100644 --- a/src/Application/TakeoutSaaS.Application/App/Tenants/Handlers/SelfRegisterTenantCommandHandler.cs +++ b/src/Application/TakeoutSaaS.Application/App/Tenants/Handlers/SelfRegisterTenantCommandHandler.cs @@ -97,7 +97,7 @@ public sealed class SelfRegisterTenantCommandHandler( await identityUserRepository.AddAsync(adminUser, cancellationToken); await identityUserRepository.SaveChangesAsync(cancellationToken); - // 7.1 (空行后) 回填主管理员标识,确保后续伪装登录/重置管理员等能力可用 + // 7.1 回填主管理员标识,确保后续伪装登录/重置管理员等能力可用 tenant.PrimaryOwnerUserId = adminUser.Id; await tenantRepository.UpdateTenantAsync(tenant, cancellationToken); await tenantRepository.SaveChangesAsync(cancellationToken); diff --git a/src/Application/TakeoutSaaS.Application/App/Tenants/Handlers/UnfreezeTenantCommandHandler.cs b/src/Application/TakeoutSaaS.Application/App/Tenants/Handlers/UnfreezeTenantCommandHandler.cs index 29409f8..b51275e 100644 --- a/src/Application/TakeoutSaaS.Application/App/Tenants/Handlers/UnfreezeTenantCommandHandler.cs +++ b/src/Application/TakeoutSaaS.Application/App/Tenants/Handlers/UnfreezeTenantCommandHandler.cs @@ -42,7 +42,7 @@ public sealed class UnfreezeTenantCommandHandler( tenant.SuspendedAt = null; tenant.SuspensionReason = null; - // 3. (空行后) 同步订阅状态 + // 3. 同步订阅状态 if (subscription != null) { subscription.Status = isExpired ? SubscriptionStatus.GracePeriod : SubscriptionStatus.Active; @@ -51,7 +51,7 @@ public sealed class UnfreezeTenantCommandHandler( await tenantRepository.UpdateTenantAsync(tenant, cancellationToken); - // 4. (空行后) 记录审计 + // 4. 记录审计 var actorName = currentUserAccessor.IsAuthenticated ? $"user:{currentUserAccessor.UserId}" : "system"; diff --git a/src/Application/TakeoutSaaS.Application/App/Tenants/Handlers/UpdateTenantCommandHandler.cs b/src/Application/TakeoutSaaS.Application/App/Tenants/Handlers/UpdateTenantCommandHandler.cs index b5c0491..a8c4d94 100644 --- a/src/Application/TakeoutSaaS.Application/App/Tenants/Handlers/UpdateTenantCommandHandler.cs +++ b/src/Application/TakeoutSaaS.Application/App/Tenants/Handlers/UpdateTenantCommandHandler.cs @@ -29,18 +29,18 @@ public sealed class UpdateTenantCommandHandler( throw new BusinessException(ErrorCodes.BadRequest, "租户名称不能为空"); } - // 2. (空行后) 查询租户 + // 2. 查询租户 var tenant = await tenantRepository.FindByIdAsync(request.TenantId, cancellationToken) ?? throw new BusinessException(ErrorCodes.NotFound, "租户不存在"); - // 3. (空行后) 校验租户名称唯一性(排除自身) + // 3. 校验租户名称唯一性(排除自身) var normalizedName = request.Name.Trim(); if (await tenantRepository.ExistsByNameAsync(normalizedName, excludeTenantId: request.TenantId, cancellationToken)) { throw new BusinessException(ErrorCodes.Conflict, $"租户名称 {normalizedName} 已存在"); } - // 4. (空行后) 校验联系人手机号唯一性(仅当填写时) + // 4. 校验联系人手机号唯一性(仅当填写时) if (!string.IsNullOrWhiteSpace(request.ContactPhone)) { var normalizedPhone = request.ContactPhone.Trim(); @@ -51,7 +51,7 @@ public sealed class UpdateTenantCommandHandler( } } - // 5. (空行后) 更新基础信息(禁止修改 Code) + // 5. 更新基础信息(禁止修改 Code) tenant.Name = normalizedName; tenant.ShortName = string.IsNullOrWhiteSpace(request.ShortName) ? null : request.ShortName.Trim(); tenant.Industry = string.IsNullOrWhiteSpace(request.Industry) ? null : request.Industry.Trim(); @@ -59,11 +59,11 @@ public sealed class UpdateTenantCommandHandler( tenant.ContactPhone = string.IsNullOrWhiteSpace(request.ContactPhone) ? null : request.ContactPhone.Trim(); tenant.ContactEmail = string.IsNullOrWhiteSpace(request.ContactEmail) ? null : request.ContactEmail.Trim(); - // 6. (空行后) 持久化更新 + // 6. 持久化更新 await tenantRepository.UpdateTenantAsync(tenant, cancellationToken); await tenantRepository.SaveChangesAsync(cancellationToken); - // 7. (空行后) 记录日志 + // 7. 记录日志 logger.LogInformation("已更新租户基础信息 {TenantId}", tenant.Id); return Unit.Value; diff --git a/src/Application/TakeoutSaaS.Application/App/Tenants/Handlers/UpdateTenantPackageCommandHandler.cs b/src/Application/TakeoutSaaS.Application/App/Tenants/Handlers/UpdateTenantPackageCommandHandler.cs index 9c17d0d..fd167de 100644 --- a/src/Application/TakeoutSaaS.Application/App/Tenants/Handlers/UpdateTenantPackageCommandHandler.cs +++ b/src/Application/TakeoutSaaS.Application/App/Tenants/Handlers/UpdateTenantPackageCommandHandler.cs @@ -45,18 +45,18 @@ public sealed class UpdateTenantPackageCommandHandler(ITenantPackageRepository p package.IsPublicVisible = request.IsPublicVisible; package.IsAllowNewTenantPurchase = request.IsAllowNewTenantPurchase; - // 3. (空行后) 更新发布状态(若未传则保持不变,避免默认值覆盖) + // 3. 更新发布状态(若未传则保持不变,避免默认值覆盖) if (request.PublishStatus.HasValue) { package.PublishStatus = request.PublishStatus.Value; } - // 4. (空行后) 更新展示配置(推荐与标签) + // 4. 更新展示配置(推荐与标签) package.IsRecommended = request.IsRecommended; package.Tags = request.Tags ?? []; package.SortOrder = request.SortOrder; - // 5. (空行后) 持久化并返回 + // 5. 持久化并返回 await packageRepository.UpdateAsync(package, cancellationToken); await packageRepository.SaveChangesAsync(cancellationToken); diff --git a/src/Application/TakeoutSaaS.Application/App/Tenants/Validators/GetTenantQuotaUsageHistoryQueryValidator.cs b/src/Application/TakeoutSaaS.Application/App/Tenants/Validators/GetTenantQuotaUsageHistoryQueryValidator.cs index 165e7f0..49ea98c 100644 --- a/src/Application/TakeoutSaaS.Application/App/Tenants/Validators/GetTenantQuotaUsageHistoryQueryValidator.cs +++ b/src/Application/TakeoutSaaS.Application/App/Tenants/Validators/GetTenantQuotaUsageHistoryQueryValidator.cs @@ -17,7 +17,7 @@ public sealed class GetTenantQuotaUsageHistoryQueryValidator : AbstractValidator RuleFor(x => x.Page).GreaterThanOrEqualTo(1); RuleFor(x => x.PageSize).InclusiveBetween(1, 100); - // (空行后) 时间范围校验 + // 时间范围校验 When(x => x.StartDate.HasValue && x.EndDate.HasValue, () => { RuleFor(x => x.EndDate!.Value).GreaterThanOrEqualTo(x => x.StartDate!.Value); diff --git a/src/Application/TakeoutSaaS.Application/Identity/Handlers/BatchIdentityUserOperationCommandHandler.cs b/src/Application/TakeoutSaaS.Application/Identity/Handlers/BatchIdentityUserOperationCommandHandler.cs index 04ee470..5a56dc0 100644 --- a/src/Application/TakeoutSaaS.Application/Identity/Handlers/BatchIdentityUserOperationCommandHandler.cs +++ b/src/Application/TakeoutSaaS.Application/Identity/Handlers/BatchIdentityUserOperationCommandHandler.cs @@ -36,7 +36,7 @@ public sealed class BatchIdentityUserOperationCommandHandler( var operatorProfile = await adminAuthService.GetProfileAsync(currentUserAccessor.UserId, cancellationToken); var isSuperAdmin = IdentityUserAccess.IsSuperAdmin(operatorProfile); - // 2. (空行后) 校验跨租户访问权限 + // 2. 校验跨租户访问权限 if (!isSuperAdmin && request.TenantId.HasValue && request.TenantId.Value != currentTenantId) { throw new BusinessException(ErrorCodes.Forbidden, "禁止跨租户批量操作用户"); @@ -47,7 +47,7 @@ public sealed class BatchIdentityUserOperationCommandHandler( throw new BusinessException(ErrorCodes.BadRequest, "批量操作必须指定租户"); } - // 3. (空行后) 解析用户 ID 列表 + // 3. 解析用户 ID 列表 var tenantId = request.TenantId ?? currentTenantId; var userIds = ParseIds(request.UserIds, "用户"); if (userIds.Length == 0) @@ -61,12 +61,12 @@ public sealed class BatchIdentityUserOperationCommandHandler( }; } - // 4. (空行后) 查询目标用户集合 + // 4. 查询目标用户集合 var includeDeleted = request.Operation == IdentityUserBatchOperation.Restore; var users = await identityUserRepository.GetForUpdateByIdsAsync(tenantId, userIds, includeDeleted, isSuperAdmin, cancellationToken); var usersById = users.ToDictionary(user => user.Id, user => user, EqualityComparer.Default); - // 5. (空行后) 预计算租户管理员约束 + // 5. 预计算租户管理员约束 var tenantAdminRole = await roleRepository.FindByCodeAsync("tenant-admin", tenantId, cancellationToken); var tenantAdminUserIds = tenantAdminRole == null ? Array.Empty() @@ -88,7 +88,7 @@ public sealed class BatchIdentityUserOperationCommandHandler( }, isSuperAdmin, cancellationToken)).Total; var remainingActiveAdmins = activeAdminCount; - // 6. (空行后) 执行批量操作 + // 6. 执行批量操作 var failures = new List(); var successCount = 0; var exportItems = new List(); @@ -185,7 +185,7 @@ public sealed class BatchIdentityUserOperationCommandHandler( }); } } - // 6.1 (空行后) 处理导出数据 + // 6.1 处理导出数据 if (request.Operation == IdentityUserBatchOperation.Export) { var roleCodesLookup = await ResolveRoleCodesAsync(users, userRoleRepository, roleRepository, cancellationToken); @@ -208,7 +208,7 @@ public sealed class BatchIdentityUserOperationCommandHandler( })); } - // 7. (空行后) 构建操作日志消息 + // 7. 构建操作日志消息 var operatorName = string.IsNullOrWhiteSpace(operatorProfile.DisplayName) ? operatorProfile.Account : operatorProfile.DisplayName; @@ -229,7 +229,7 @@ public sealed class BatchIdentityUserOperationCommandHandler( Success = failures.Count == 0 }; - // 8. (空行后) 写入 Outbox 并保存变更 + // 8. 写入 Outbox 并保存变更 await operationLogPublisher.PublishAsync(logMessage, cancellationToken); await identityUserRepository.SaveChangesAsync(cancellationToken); @@ -250,7 +250,7 @@ public sealed class BatchIdentityUserOperationCommandHandler( return Array.Empty(); } - // 2. (空行后) 解析并去重 + // 2. 解析并去重 var ids = new List(values.Length); foreach (var value in values) { @@ -262,7 +262,7 @@ public sealed class BatchIdentityUserOperationCommandHandler( ids.Add(id); } - // 3. (空行后) 返回去重结果 + // 3. 返回去重结果 return ids.Distinct().ToArray(); } @@ -275,7 +275,7 @@ public sealed class BatchIdentityUserOperationCommandHandler( // 1. 预分配字典容量 var result = new Dictionary(users.Count); - // 2. (空行后) 按租户分组,降低角色查询次数 + // 2. 按租户分组,降低角色查询次数 foreach (var group in users.GroupBy(user => user.TenantId)) { var tenantId = group.Key; @@ -285,21 +285,21 @@ public sealed class BatchIdentityUserOperationCommandHandler( continue; } - // 3. (空行后) 查询用户角色映射 + // 3. 查询用户角色映射 var relations = await userRoleRepository.GetByUserIdsAsync(tenantId, userIds, cancellationToken); if (relations.Count == 0) { continue; } - // 4. (空行后) 查询角色并构建映射 + // 4. 查询角色并构建映射 var roleIds = relations.Select(x => x.RoleId).Distinct().ToArray(); var roles = roleIds.Length == 0 ? Array.Empty() : await roleRepository.GetByIdsAsync(tenantId, roleIds, cancellationToken); var roleCodeMap = roles.ToDictionary(role => role.Id, role => role.Code, EqualityComparer.Default); - // 5. (空行后) 组装用户角色编码列表 + // 5. 组装用户角色编码列表 foreach (var relationGroup in relations.GroupBy(x => x.UserId)) { var codes = relationGroup diff --git a/src/Application/TakeoutSaaS.Application/Identity/Handlers/ChangeIdentityUserStatusCommandHandler.cs b/src/Application/TakeoutSaaS.Application/Identity/Handlers/ChangeIdentityUserStatusCommandHandler.cs index 11da3a9..0390a09 100644 --- a/src/Application/TakeoutSaaS.Application/Identity/Handlers/ChangeIdentityUserStatusCommandHandler.cs +++ b/src/Application/TakeoutSaaS.Application/Identity/Handlers/ChangeIdentityUserStatusCommandHandler.cs @@ -33,13 +33,13 @@ public sealed class ChangeIdentityUserStatusCommandHandler( var operatorProfile = await adminAuthService.GetProfileAsync(currentUserAccessor.UserId, cancellationToken); var isSuperAdmin = IdentityUserAccess.IsSuperAdmin(operatorProfile); - // 2. (空行后) 校验跨租户访问权限 + // 2. 校验跨租户访问权限 if (!isSuperAdmin && request.TenantId.HasValue && request.TenantId.Value != currentTenantId) { throw new BusinessException(ErrorCodes.Forbidden, "禁止跨租户修改用户状态"); } - // 3. (空行后) 查询用户实体 + // 3. 查询用户实体 var user = isSuperAdmin ? await identityUserRepository.GetForUpdateIgnoringTenantAsync(request.UserId, cancellationToken) : await identityUserRepository.GetForUpdateAsync(request.UserId, cancellationToken); @@ -53,13 +53,13 @@ public sealed class ChangeIdentityUserStatusCommandHandler( return false; } - // 4. (空行后) 校验租户管理员保留规则 + // 4. 校验租户管理员保留规则 if (request.Status == IdentityUserStatus.Disabled && user.Status == IdentityUserStatus.Active) { await EnsureNotLastActiveTenantAdminAsync(user.TenantId, user.Id, isSuperAdmin, cancellationToken); } - // 5. (空行后) 更新状态 + // 5. 更新状态 var previousStatus = user.Status; switch (request.Status) { @@ -80,7 +80,7 @@ public sealed class ChangeIdentityUserStatusCommandHandler( throw new BusinessException(ErrorCodes.BadRequest, "无效的用户状态"); } - // 6. (空行后) 构建操作日志消息 + // 6. 构建操作日志消息 var operatorName = string.IsNullOrWhiteSpace(operatorProfile.DisplayName) ? operatorProfile.Account : operatorProfile.DisplayName; @@ -107,7 +107,7 @@ public sealed class ChangeIdentityUserStatusCommandHandler( Success = true }; - // 7. (空行后) 写入 Outbox 并保存变更 + // 7. 写入 Outbox 并保存变更 await operationLogPublisher.PublishAsync(logMessage, cancellationToken); await identityUserRepository.SaveChangesAsync(cancellationToken); @@ -123,14 +123,14 @@ public sealed class ChangeIdentityUserStatusCommandHandler( return; } - // 2. (空行后) 判断用户是否为租户管理员 + // 2. 判断用户是否为租户管理员 var relations = await userRoleRepository.GetByUserIdAsync(tenantId, userId, cancellationToken); if (!relations.Any(x => x.RoleId == tenantAdminRole.Id)) { return; } - // 3. (空行后) 统计活跃管理员数量 + // 3. 统计活跃管理员数量 var filter = new IdentityUserSearchFilter { TenantId = tenantId, diff --git a/src/Application/TakeoutSaaS.Application/Identity/Handlers/CreateIdentityUserCommandHandler.cs b/src/Application/TakeoutSaaS.Application/Identity/Handlers/CreateIdentityUserCommandHandler.cs index edc5d3f..00d38c9 100644 --- a/src/Application/TakeoutSaaS.Application/Identity/Handlers/CreateIdentityUserCommandHandler.cs +++ b/src/Application/TakeoutSaaS.Application/Identity/Handlers/CreateIdentityUserCommandHandler.cs @@ -41,13 +41,13 @@ public sealed class CreateIdentityUserCommandHandler( var operatorProfile = await adminAuthService.GetProfileAsync(currentUserAccessor.UserId, cancellationToken); var isSuperAdmin = IdentityUserAccess.IsSuperAdmin(operatorProfile); - // 2. (空行后) 校验跨租户访问权限 + // 2. 校验跨租户访问权限 if (!isSuperAdmin && request.TenantId.HasValue && request.TenantId.Value != currentTenantId) { throw new BusinessException(ErrorCodes.Forbidden, "禁止跨租户创建用户"); } - // 3. (空行后) 规范化输入并准备校验 + // 3. 规范化输入并准备校验 var tenantId = isSuperAdmin ? request.TenantId ?? currentTenantId : currentTenantId; var account = request.Account.Trim(); var displayName = request.DisplayName.Trim(); @@ -55,7 +55,7 @@ public sealed class CreateIdentityUserCommandHandler( var email = string.IsNullOrWhiteSpace(request.Email) ? null : request.Email.Trim(); var roleIds = ParseIds(request.RoleIds, "角色"); - // 4. (空行后) 唯一性校验 + // 4. 唯一性校验 if (await identityUserRepository.ExistsByAccountAsync(tenantId, account, null, cancellationToken)) { throw new BusinessException(ErrorCodes.Conflict, "账号已存在"); @@ -73,7 +73,7 @@ public sealed class CreateIdentityUserCommandHandler( throw new BusinessException(ErrorCodes.Conflict, "邮箱已存在"); } - // 5. (空行后) 校验角色合法性 + // 5. 校验角色合法性 if (roleIds.Length > 0) { var roles = await roleRepository.GetByIdsAsync(tenantId, roleIds, cancellationToken); @@ -83,7 +83,7 @@ public sealed class CreateIdentityUserCommandHandler( } } - // 6. (空行后) 创建用户实体 + // 6. 创建用户实体 var user = new IdentityUser { Id = idGenerator.NextId(), @@ -102,7 +102,7 @@ public sealed class CreateIdentityUserCommandHandler( }; user.PasswordHash = passwordHasher.HashPassword(user, request.Password); - // 7. (空行后) 构建操作日志消息 + // 7. 构建操作日志消息 var operatorName = string.IsNullOrWhiteSpace(operatorProfile.DisplayName) ? operatorProfile.Account : operatorProfile.DisplayName; @@ -131,18 +131,18 @@ public sealed class CreateIdentityUserCommandHandler( Success = true }; - // 8. (空行后) 持久化用户并写入 Outbox + // 8. 持久化用户并写入 Outbox await identityUserRepository.AddAsync(user, cancellationToken); await operationLogPublisher.PublishAsync(logMessage, cancellationToken); await identityUserRepository.SaveChangesAsync(cancellationToken); - // 9. (空行后) 绑定角色 + // 9. 绑定角色 if (roleIds.Length > 0) { await userRoleRepository.ReplaceUserRolesAsync(tenantId, user.Id, roleIds, cancellationToken); } - // 10. (空行后) 返回用户详情 + // 10. 返回用户详情 var detail = await mediator.Send(new GetIdentityUserDetailQuery { UserId = user.Id }, cancellationToken); return detail ?? new UserDetailDto { @@ -173,7 +173,7 @@ public sealed class CreateIdentityUserCommandHandler( return Array.Empty(); } - // 2. (空行后) 解析并去重 + // 2. 解析并去重 var ids = new List(values.Length); foreach (var value in values) { @@ -185,7 +185,7 @@ public sealed class CreateIdentityUserCommandHandler( ids.Add(id); } - // 3. (空行后) 返回去重结果 + // 3. 返回去重结果 return ids.Distinct().ToArray(); } } diff --git a/src/Application/TakeoutSaaS.Application/Identity/Handlers/DeleteIdentityUserCommandHandler.cs b/src/Application/TakeoutSaaS.Application/Identity/Handlers/DeleteIdentityUserCommandHandler.cs index 74d3eba..e452305 100644 --- a/src/Application/TakeoutSaaS.Application/Identity/Handlers/DeleteIdentityUserCommandHandler.cs +++ b/src/Application/TakeoutSaaS.Application/Identity/Handlers/DeleteIdentityUserCommandHandler.cs @@ -33,13 +33,13 @@ public sealed class DeleteIdentityUserCommandHandler( var operatorProfile = await adminAuthService.GetProfileAsync(currentUserAccessor.UserId, cancellationToken); var isSuperAdmin = IdentityUserAccess.IsSuperAdmin(operatorProfile); - // 2. (空行后) 校验跨租户访问权限 + // 2. 校验跨租户访问权限 if (!isSuperAdmin && request.TenantId.HasValue && request.TenantId.Value != currentTenantId) { throw new BusinessException(ErrorCodes.Forbidden, "禁止跨租户删除用户"); } - // 3. (空行后) 查询用户实体 + // 3. 查询用户实体 var user = isSuperAdmin ? await identityUserRepository.GetForUpdateIgnoringTenantAsync(request.UserId, cancellationToken) : await identityUserRepository.GetForUpdateAsync(request.UserId, cancellationToken); @@ -53,13 +53,13 @@ public sealed class DeleteIdentityUserCommandHandler( return false; } - // 4. (空行后) 校验租户管理员保留规则 + // 4. 校验租户管理员保留规则 if (user.Status == IdentityUserStatus.Active) { await EnsureNotLastActiveTenantAdminAsync(user.TenantId, user.Id, isSuperAdmin, cancellationToken); } - // 5. (空行后) 构建操作日志消息 + // 5. 构建操作日志消息 var operatorName = string.IsNullOrWhiteSpace(operatorProfile.DisplayName) ? operatorProfile.Account : operatorProfile.DisplayName; @@ -80,7 +80,7 @@ public sealed class DeleteIdentityUserCommandHandler( Success = true }; - // 6. (空行后) 软删除用户并写入 Outbox + // 6. 软删除用户并写入 Outbox await identityUserRepository.RemoveAsync(user, cancellationToken); await operationLogPublisher.PublishAsync(logMessage, cancellationToken); await identityUserRepository.SaveChangesAsync(cancellationToken); @@ -97,14 +97,14 @@ public sealed class DeleteIdentityUserCommandHandler( return; } - // 2. (空行后) 判断用户是否为租户管理员 + // 2. 判断用户是否为租户管理员 var relations = await userRoleRepository.GetByUserIdAsync(tenantId, userId, cancellationToken); if (!relations.Any(x => x.RoleId == tenantAdminRole.Id)) { return; } - // 3. (空行后) 统计活跃管理员数量 + // 3. 统计活跃管理员数量 var filter = new IdentityUserSearchFilter { TenantId = tenantId, diff --git a/src/Application/TakeoutSaaS.Application/Identity/Handlers/GetIdentityUserDetailQueryHandler.cs b/src/Application/TakeoutSaaS.Application/Identity/Handlers/GetIdentityUserDetailQueryHandler.cs index b874124..87d1aad 100644 --- a/src/Application/TakeoutSaaS.Application/Identity/Handlers/GetIdentityUserDetailQueryHandler.cs +++ b/src/Application/TakeoutSaaS.Application/Identity/Handlers/GetIdentityUserDetailQueryHandler.cs @@ -32,7 +32,7 @@ public sealed class GetIdentityUserDetailQueryHandler( var operatorProfile = await adminAuthService.GetProfileAsync(currentUserAccessor.UserId, cancellationToken); var isSuperAdmin = IdentityUserAccess.IsSuperAdmin(operatorProfile); - // 2. (空行后) 查询用户实体 + // 2. 查询用户实体 IdentityUser? user; if (request.IncludeDeleted) { @@ -55,7 +55,7 @@ public sealed class GetIdentityUserDetailQueryHandler( return null; } - // 3. (空行后) 加载角色与权限 + // 3. 加载角色与权限 var roleRelations = await userRoleRepository.GetByUserIdAsync(user.TenantId, user.Id, cancellationToken); var roleIds = roleRelations.Select(x => x.RoleId).Distinct().ToArray(); var roles = roleIds.Length == 0 @@ -79,7 +79,7 @@ public sealed class GetIdentityUserDetailQueryHandler( .Distinct(StringComparer.OrdinalIgnoreCase) .ToArray(); - // 4. (空行后) 组装详情 DTO + // 4. 组装详情 DTO var now = DateTime.UtcNow; return new UserDetailDto { diff --git a/src/Application/TakeoutSaaS.Application/Identity/Handlers/ResetAdminPasswordByTokenCommandHandler.cs b/src/Application/TakeoutSaaS.Application/Identity/Handlers/ResetAdminPasswordByTokenCommandHandler.cs index 1280999..ad9bc80 100644 --- a/src/Application/TakeoutSaaS.Application/Identity/Handlers/ResetAdminPasswordByTokenCommandHandler.cs +++ b/src/Application/TakeoutSaaS.Application/Identity/Handlers/ResetAdminPasswordByTokenCommandHandler.cs @@ -40,14 +40,14 @@ public sealed class ResetAdminPasswordByTokenCommandHandler( throw new BusinessException(ErrorCodes.BadRequest, "新密码长度需为 6~32 位"); } - // 2. (空行后) 校验并消费令牌 + // 2. 校验并消费令牌 var userId = await tokenStore.ConsumeAsync(token, cancellationToken); if (!userId.HasValue || userId.Value == 0) { throw new BusinessException(ErrorCodes.BadRequest, "重置链接无效或已过期"); } - // 3. (空行后) 获取用户(可更新,忽略租户过滤器)并写入新密码哈希 + // 3. 获取用户(可更新,忽略租户过滤器)并写入新密码哈希 var user = await userRepository.GetForUpdateIgnoringTenantAsync(userId.Value, cancellationToken) ?? throw new BusinessException(ErrorCodes.NotFound, "用户不存在"); diff --git a/src/Application/TakeoutSaaS.Application/Identity/Handlers/ResetIdentityUserPasswordCommandHandler.cs b/src/Application/TakeoutSaaS.Application/Identity/Handlers/ResetIdentityUserPasswordCommandHandler.cs index 2cb7ac6..6f57f76 100644 --- a/src/Application/TakeoutSaaS.Application/Identity/Handlers/ResetIdentityUserPasswordCommandHandler.cs +++ b/src/Application/TakeoutSaaS.Application/Identity/Handlers/ResetIdentityUserPasswordCommandHandler.cs @@ -33,13 +33,13 @@ public sealed class ResetIdentityUserPasswordCommandHandler( var operatorProfile = await adminAuthService.GetProfileAsync(currentUserAccessor.UserId, cancellationToken); var isSuperAdmin = IdentityUserAccess.IsSuperAdmin(operatorProfile); - // 2. (空行后) 校验跨租户访问权限 + // 2. 校验跨租户访问权限 if (!isSuperAdmin && request.TenantId.HasValue && request.TenantId.Value != currentTenantId) { throw new BusinessException(ErrorCodes.Forbidden, "禁止跨租户重置密码"); } - // 3. (空行后) 查询用户实体 + // 3. 查询用户实体 var user = isSuperAdmin ? await identityUserRepository.GetForUpdateIgnoringTenantAsync(request.UserId, cancellationToken) : await identityUserRepository.GetForUpdateAsync(request.UserId, cancellationToken); @@ -53,11 +53,11 @@ public sealed class ResetIdentityUserPasswordCommandHandler( throw new BusinessException(ErrorCodes.Forbidden, "禁止跨租户重置密码"); } - // 4. (空行后) 签发重置令牌(1 小时有效) + // 4. 签发重置令牌(1 小时有效) var expiresAt = DateTime.UtcNow.AddHours(1); var token = await tokenStore.IssueAsync(user.Id, expiresAt, cancellationToken); - // 5. (空行后) 标记用户需重置密码 + // 5. 标记用户需重置密码 user.MustChangePassword = true; user.FailedLoginCount = 0; user.LockedUntil = null; @@ -66,7 +66,7 @@ public sealed class ResetIdentityUserPasswordCommandHandler( user.Status = IdentityUserStatus.Active; } - // 6. (空行后) 构建操作日志消息 + // 6. 构建操作日志消息 var operatorName = string.IsNullOrWhiteSpace(operatorProfile.DisplayName) ? operatorProfile.Account : operatorProfile.DisplayName; @@ -87,7 +87,7 @@ public sealed class ResetIdentityUserPasswordCommandHandler( Success = true }; - // 7. (空行后) 写入 Outbox 并保存变更 + // 7. 写入 Outbox 并保存变更 await operationLogPublisher.PublishAsync(logMessage, cancellationToken); await identityUserRepository.SaveChangesAsync(cancellationToken); diff --git a/src/Application/TakeoutSaaS.Application/Identity/Handlers/RestoreIdentityUserCommandHandler.cs b/src/Application/TakeoutSaaS.Application/Identity/Handlers/RestoreIdentityUserCommandHandler.cs index 67ebe55..525341a 100644 --- a/src/Application/TakeoutSaaS.Application/Identity/Handlers/RestoreIdentityUserCommandHandler.cs +++ b/src/Application/TakeoutSaaS.Application/Identity/Handlers/RestoreIdentityUserCommandHandler.cs @@ -30,13 +30,13 @@ public sealed class RestoreIdentityUserCommandHandler( var operatorProfile = await adminAuthService.GetProfileAsync(currentUserAccessor.UserId, cancellationToken); var isSuperAdmin = IdentityUserAccess.IsSuperAdmin(operatorProfile); - // 2. (空行后) 校验跨租户访问权限 + // 2. 校验跨租户访问权限 if (!isSuperAdmin && request.TenantId.HasValue && request.TenantId.Value != currentTenantId) { throw new BusinessException(ErrorCodes.Forbidden, "禁止跨租户恢复用户"); } - // 3. (空行后) 查询用户实体(包含已删除) + // 3. 查询用户实体(包含已删除) var user = await identityUserRepository.GetForUpdateIncludingDeletedAsync(currentTenantId, request.UserId, isSuperAdmin, cancellationToken); if (user == null) { @@ -53,7 +53,7 @@ public sealed class RestoreIdentityUserCommandHandler( return false; } - // 4. (空行后) 构建操作日志消息 + // 4. 构建操作日志消息 var operatorName = string.IsNullOrWhiteSpace(operatorProfile.DisplayName) ? operatorProfile.Account : operatorProfile.DisplayName; @@ -74,7 +74,7 @@ public sealed class RestoreIdentityUserCommandHandler( Success = true }; - // 5. (空行后) 恢复软删除状态并写入 Outbox + // 5. 恢复软删除状态并写入 Outbox user.DeletedAt = null; user.DeletedBy = null; await operationLogPublisher.PublishAsync(logMessage, cancellationToken); diff --git a/src/Application/TakeoutSaaS.Application/Identity/Handlers/SearchIdentityUsersQueryHandler.cs b/src/Application/TakeoutSaaS.Application/Identity/Handlers/SearchIdentityUsersQueryHandler.cs index 2b880c3..887136e 100644 --- a/src/Application/TakeoutSaaS.Application/Identity/Handlers/SearchIdentityUsersQueryHandler.cs +++ b/src/Application/TakeoutSaaS.Application/Identity/Handlers/SearchIdentityUsersQueryHandler.cs @@ -33,13 +33,13 @@ public sealed class SearchIdentityUsersQueryHandler( var operatorProfile = await adminAuthService.GetProfileAsync(currentUserAccessor.UserId, cancellationToken); var isSuperAdmin = IdentityUserAccess.IsSuperAdmin(operatorProfile); - // 2. (空行后) 校验跨租户访问权限 + // 2. 校验跨租户访问权限 if (!isSuperAdmin && request.TenantId.HasValue && request.TenantId.Value != currentTenantId) { throw new BusinessException(ErrorCodes.Forbidden, "禁止跨租户查询用户"); } - // 3. (空行后) 组装查询过滤条件 + // 3. 组装查询过滤条件 var filter = new IdentityUserSearchFilter { TenantId = isSuperAdmin ? request.TenantId : currentTenantId, @@ -57,17 +57,17 @@ public sealed class SearchIdentityUsersQueryHandler( SortDescending = request.SortDescending }; - // 4. (空行后) 执行分页查询 + // 4. 执行分页查询 var (items, total) = await identityUserRepository.SearchPagedAsync(filter, isSuperAdmin, cancellationToken); if (items.Count == 0) { return new PagedResult(Array.Empty(), request.Page, request.PageSize, total); } - // 5. (空行后) 加载角色编码映射 + // 5. 加载角色编码映射 var roleCodesLookup = await ResolveRoleCodesAsync(items, userRoleRepository, roleRepository, cancellationToken); - // 6. (空行后) 组装 DTO + // 6. 组装 DTO var now = DateTime.UtcNow; var dtos = items.Select(user => new UserListItemDto { @@ -86,7 +86,7 @@ public sealed class SearchIdentityUsersQueryHandler( LastLoginAt = user.LastLoginAt }).ToList(); - // 7. (空行后) 返回分页结果 + // 7. 返回分页结果 return new PagedResult(dtos, request.Page, request.PageSize, total); } @@ -103,7 +103,7 @@ public sealed class SearchIdentityUsersQueryHandler( // 1. 预分配字典容量 var result = new Dictionary(users.Count); - // 2. (空行后) 按租户分组,降低角色查询次数 + // 2. 按租户分组,降低角色查询次数 foreach (var group in users.GroupBy(user => user.TenantId)) { var tenantId = group.Key; @@ -113,21 +113,21 @@ public sealed class SearchIdentityUsersQueryHandler( continue; } - // 3. (空行后) 查询用户角色映射 + // 3. 查询用户角色映射 var relations = await userRoleRepository.GetByUserIdsAsync(tenantId, userIds, cancellationToken); if (relations.Count == 0) { continue; } - // 4. (空行后) 查询角色并构建映射 + // 4. 查询角色并构建映射 var roleIds = relations.Select(x => x.RoleId).Distinct().ToArray(); var roles = roleIds.Length == 0 ? Array.Empty() : await roleRepository.GetByIdsAsync(tenantId, roleIds, cancellationToken); var roleCodeMap = roles.ToDictionary(role => role.Id, role => role.Code, EqualityComparer.Default); - // 5. (空行后) 组装用户角色编码列表 + // 5. 组装用户角色编码列表 foreach (var relationGroup in relations.GroupBy(x => x.UserId)) { var codes = relationGroup diff --git a/src/Application/TakeoutSaaS.Application/Identity/Handlers/UpdateIdentityUserCommandHandler.cs b/src/Application/TakeoutSaaS.Application/Identity/Handlers/UpdateIdentityUserCommandHandler.cs index 2f771e7..fefa002 100644 --- a/src/Application/TakeoutSaaS.Application/Identity/Handlers/UpdateIdentityUserCommandHandler.cs +++ b/src/Application/TakeoutSaaS.Application/Identity/Handlers/UpdateIdentityUserCommandHandler.cs @@ -36,13 +36,13 @@ public sealed class UpdateIdentityUserCommandHandler( var operatorProfile = await adminAuthService.GetProfileAsync(currentUserAccessor.UserId, cancellationToken); var isSuperAdmin = IdentityUserAccess.IsSuperAdmin(operatorProfile); - // 2. (空行后) 校验跨租户访问权限 + // 2. 校验跨租户访问权限 if (!isSuperAdmin && request.TenantId.HasValue && request.TenantId.Value != currentTenantId) { throw new BusinessException(ErrorCodes.Forbidden, "禁止跨租户更新用户"); } - // 3. (空行后) 获取用户实体 + // 3. 获取用户实体 var user = isSuperAdmin ? await identityUserRepository.GetForUpdateIgnoringTenantAsync(request.UserId, cancellationToken) : await identityUserRepository.GetForUpdateAsync(request.UserId, cancellationToken); @@ -56,7 +56,7 @@ public sealed class UpdateIdentityUserCommandHandler( return null; } - // 4. (空行后) 规范化输入并校验唯一性 + // 4. 规范化输入并校验唯一性 var displayName = request.DisplayName.Trim(); var phone = string.IsNullOrWhiteSpace(request.Phone) ? null : request.Phone.Trim(); var email = string.IsNullOrWhiteSpace(request.Email) ? null : request.Email.Trim(); @@ -85,14 +85,14 @@ public sealed class UpdateIdentityUserCommandHandler( } } - // 5. (空行后) 更新用户字段 + // 5. 更新用户字段 user.DisplayName = displayName; user.Phone = phone; user.Email = email; user.Avatar = string.IsNullOrWhiteSpace(request.Avatar) ? null : request.Avatar.Trim(); user.RowVersion = request.RowVersion; - // 6. (空行后) 构建操作日志消息 + // 6. 构建操作日志消息 var operatorName = string.IsNullOrWhiteSpace(operatorProfile.DisplayName) ? operatorProfile.Account : operatorProfile.DisplayName; @@ -120,7 +120,7 @@ public sealed class UpdateIdentityUserCommandHandler( Success = true }; - // 7. (空行后) 持久化用户更新并写入 Outbox + // 7. 持久化用户更新并写入 Outbox try { await operationLogPublisher.PublishAsync(logMessage, cancellationToken); @@ -131,13 +131,13 @@ public sealed class UpdateIdentityUserCommandHandler( throw new BusinessException(ErrorCodes.Conflict, "用户数据已被修改,请刷新后重试"); } - // 8. (空行后) 覆盖角色绑定(仅当显式传入时) + // 8. 覆盖角色绑定(仅当显式传入时) if (roleIds != null) { await userRoleRepository.ReplaceUserRolesAsync(user.TenantId, user.Id, roleIds, cancellationToken); } - // 9. (空行后) 返回用户详情 + // 9. 返回用户详情 return await mediator.Send(new GetIdentityUserDetailQuery { UserId = user.Id }, cancellationToken); } @@ -149,7 +149,7 @@ public sealed class UpdateIdentityUserCommandHandler( return Array.Empty(); } - // 2. (空行后) 解析并去重 + // 2. 解析并去重 var ids = new List(values.Length); foreach (var value in values) { @@ -161,7 +161,7 @@ public sealed class UpdateIdentityUserCommandHandler( ids.Add(id); } - // 3. (空行后) 返回去重结果 + // 3. 返回去重结果 return ids.Distinct().ToArray(); } diff --git a/src/Application/TakeoutSaaS.Application/Identity/Services/AdminAuthService.cs b/src/Application/TakeoutSaaS.Application/Identity/Services/AdminAuthService.cs index aeee4e7..43177be 100644 --- a/src/Application/TakeoutSaaS.Application/Identity/Services/AdminAuthService.cs +++ b/src/Application/TakeoutSaaS.Application/Identity/Services/AdminAuthService.cs @@ -81,10 +81,10 @@ public sealed class AdminAuthService( throw new BusinessException(ErrorCodes.Unauthorized, "账号或密码错误"); } - // 4. (空行后) 更新登录成功状态 + // 4. 更新登录成功状态 await UpdateLoginSuccessAsync(user.Id, now, cancellationToken); - // 5. (空行后) 构建用户档案并生成令牌 + // 5. 构建用户档案并生成令牌 var profile = await BuildProfileAsync(user, cancellationToken); return await jwtTokenService.CreateTokensAsync(profile, false, cancellationToken); } diff --git a/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Persistence/Configurations/TenantBillingStatementConfiguration.cs b/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Persistence/Configurations/TenantBillingStatementConfiguration.cs index 2d17cd5..d40970f 100644 --- a/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Persistence/Configurations/TenantBillingStatementConfiguration.cs +++ b/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Persistence/Configurations/TenantBillingStatementConfiguration.cs @@ -26,25 +26,25 @@ public sealed class TenantBillingStatementConfiguration : IEntityTypeConfigurati builder.Property(x => x.Currency).HasMaxLength(8).HasDefaultValue("CNY"); builder.Property(x => x.Status).HasConversion(); - // 2. (空行后) JSON 字段(当前以 text 存储 JSON 字符串,便于兼容历史迁移) + // 2. JSON 字段(当前以 text 存储 JSON 字符串,便于兼容历史迁移) builder.Property(x => x.LineItemsJson).HasColumnType("text"); - // 3. (空行后) 备注字段 + // 3. 备注字段 builder.Property(x => x.Notes).HasMaxLength(512); - // 4. (空行后) 唯一约束与索引 + // 4. 唯一约束与索引 builder.HasIndex(x => new { x.TenantId, x.StatementNo }).IsUnique(); - // 5. (空行后) 性能索引(高频查询:租户+状态+到期日) + // 5. 性能索引(高频查询:租户+状态+到期日) builder.HasIndex(x => new { x.TenantId, x.Status, x.DueDate }) .HasDatabaseName("idx_billing_tenant_status_duedate"); - // 6. (空行后) 逾期扫描索引(仅索引 Pending/Overdue) + // 6. 逾期扫描索引(仅索引 Pending/Overdue) builder.HasIndex(x => new { x.Status, x.DueDate }) .HasDatabaseName("idx_billing_status_duedate") .HasFilter($"\"Status\" IN ({(int)TenantBillingStatus.Pending}, {(int)TenantBillingStatus.Overdue})"); - // 7. (空行后) 创建时间索引(支持列表倒序) + // 7. 创建时间索引(支持列表倒序) builder.HasIndex(x => x.CreatedAt) .HasDatabaseName("idx_billing_created_at"); } diff --git a/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Persistence/Configurations/TenantPaymentConfiguration.cs b/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Persistence/Configurations/TenantPaymentConfiguration.cs index 97aac3a..114a31c 100644 --- a/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Persistence/Configurations/TenantPaymentConfiguration.cs +++ b/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Persistence/Configurations/TenantPaymentConfiguration.cs @@ -25,14 +25,14 @@ public sealed class TenantPaymentConfiguration : IEntityTypeConfiguration x.RefundReason).HasMaxLength(512); builder.Property(x => x.Notes).HasMaxLength(512); - // 2. (空行后) 复合索引:租户+账单 + // 2. 复合索引:租户+账单 builder.HasIndex(x => new { x.TenantId, x.BillingStatementId }); - // 3. (空行后) 支付记录时间排序索引 + // 3. 支付记录时间排序索引 builder.HasIndex(x => new { x.BillingStatementId, x.PaidAt }) .HasDatabaseName("idx_payment_billing_paidat"); - // 4. (空行后) 交易号索引(部分索引:仅非空) + // 4. 交易号索引(部分索引:仅非空) builder.HasIndex(x => x.TransactionNo) .HasDatabaseName("idx_payment_transaction_no") .HasFilter("\"TransactionNo\" IS NOT NULL"); diff --git a/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Persistence/Repositories/TenantBillingRepository.cs b/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Persistence/Repositories/TenantBillingRepository.cs index 0dc570b..7d8d382 100644 --- a/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Persistence/Repositories/TenantBillingRepository.cs +++ b/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Persistence/Repositories/TenantBillingRepository.cs @@ -25,13 +25,13 @@ public sealed class TenantBillingRepository(TakeoutAppDbContext context) : ITena .AsNoTracking() .Where(x => x.DeletedAt == null && x.TenantId == tenantId); - // 2. (空行后) 按状态过滤 + // 2. 按状态过滤 if (status.HasValue) { query = query.Where(x => x.Status == status.Value); } - // 3. (空行后) 按日期范围过滤(账单周期) + // 3. 按日期范围过滤(账单周期) if (from.HasValue) { query = query.Where(x => x.PeriodStart >= from.Value); @@ -42,7 +42,7 @@ public sealed class TenantBillingRepository(TakeoutAppDbContext context) : ITena query = query.Where(x => x.PeriodEnd <= to.Value); } - // 4. (空行后) 排序返回 + // 4. 排序返回 return await query .OrderByDescending(x => x.PeriodEnd) .ToListAsync(cancellationToken); @@ -102,7 +102,7 @@ public sealed class TenantBillingRepository(TakeoutAppDbContext context) : ITena // 1. 以当前 UTC 时间作为逾期判断基准 var now = DateTime.UtcNow; - // 2. (空行后) 查询逾期且仍处于待支付的账单(仅 Pending 才允许自动切换为 Overdue) + // 2. 查询逾期且仍处于待支付的账单(仅 Pending 才允许自动切换为 Overdue) return await context.TenantBillingStatements .IgnoreQueryFilters() .AsNoTracking() @@ -120,7 +120,7 @@ public sealed class TenantBillingRepository(TakeoutAppDbContext context) : ITena var now = DateTime.UtcNow; var dueTo = now.AddDays(daysAhead); - // 2. (空行后) 仅查询待支付账单 + // 2. 仅查询待支付账单 return await context.TenantBillingStatements .IgnoreQueryFilters() .AsNoTracking() @@ -198,19 +198,19 @@ public sealed class TenantBillingRepository(TakeoutAppDbContext context) : ITena .AsNoTracking() .Where(x => x.DeletedAt == null); - // 2. (空行后) 按租户过滤(可选) + // 2. 按租户过滤(可选) if (tenantId.HasValue) { query = query.Where(x => x.TenantId == tenantId.Value); } - // 3. (空行后) 按状态过滤(可选) + // 3. 按状态过滤(可选) if (status.HasValue) { query = query.Where(x => x.Status == status.Value); } - // 4. (空行后) 按日期范围过滤(账单周期) + // 4. 按日期范围过滤(账单周期) if (from.HasValue) { query = query.Where(x => x.PeriodStart >= from.Value); @@ -221,7 +221,7 @@ public sealed class TenantBillingRepository(TakeoutAppDbContext context) : ITena query = query.Where(x => x.PeriodEnd <= to.Value); } - // 5. (空行后) 按金额范围过滤(应付金额,包含边界) + // 5. 按金额范围过滤(应付金额,包含边界) if (minAmount.HasValue) { query = query.Where(x => x.AmountDue >= minAmount.Value); @@ -232,7 +232,7 @@ public sealed class TenantBillingRepository(TakeoutAppDbContext context) : ITena query = query.Where(x => x.AmountDue <= maxAmount.Value); } - // 6. (空行后) 关键字过滤(账单号或租户名) + // 6. 关键字过滤(账单号或租户名) if (!string.IsNullOrWhiteSpace(keyword)) { var normalized = keyword.Trim(); @@ -249,10 +249,10 @@ public sealed class TenantBillingRepository(TakeoutAppDbContext context) : ITena select b; } - // 7. (空行后) 统计总数 + // 7. 统计总数 var total = await query.CountAsync(cancellationToken); - // 8. (空行后) 分页查询 + // 8. 分页查询 var items = await query .OrderByDescending(x => x.PeriodEnd) .Skip((pageNumber - 1) * pageSize) @@ -279,7 +279,7 @@ public sealed class TenantBillingRepository(TakeoutAppDbContext context) : ITena && x.PeriodStart >= startDate && x.PeriodEnd <= endDate); - // 2. (空行后) 聚合统计(金额统一使用:应付 - 折扣 + 税费) + // 2. 聚合统计(金额统一使用:应付 - 折扣 + 税费) var now = DateTime.UtcNow; var totalAmount = await query.SumAsync(x => x.AmountDue - x.DiscountAmount + x.TaxAmount, cancellationToken); var paidAmount = await query.Where(x => x.Status == TenantBillingStatus.Paid).SumAsync(x => x.AmountPaid, cancellationToken); @@ -288,13 +288,13 @@ public sealed class TenantBillingRepository(TakeoutAppDbContext context) : ITena .Where(x => (x.Status == TenantBillingStatus.Pending || x.Status == TenantBillingStatus.Overdue) && x.DueDate < now) .SumAsync(x => (x.AmountDue - x.DiscountAmount + x.TaxAmount) - x.AmountPaid, cancellationToken); - // 3. (空行后) 数量统计 + // 3. 数量统计 var totalCount = await query.CountAsync(cancellationToken); var paidCount = await query.CountAsync(x => x.Status == TenantBillingStatus.Paid, cancellationToken); var unpaidCount = await query.CountAsync(x => x.Status == TenantBillingStatus.Pending || x.Status == TenantBillingStatus.Overdue, cancellationToken); var overdueCount = await query.CountAsync(x => (x.Status == TenantBillingStatus.Pending || x.Status == TenantBillingStatus.Overdue) && x.DueDate < now, cancellationToken); - // 4. (空行后) 趋势统计 + // 4. 趋势统计 var normalizedGroupBy = NormalizeGroupBy(groupBy); var trendRaw = await query .Select(x => new @@ -307,7 +307,7 @@ public sealed class TenantBillingRepository(TakeoutAppDbContext context) : ITena }) .ToListAsync(cancellationToken); - // 4.1 (空行后) 在内存中按 Day/Week/Month 聚合(避免依赖特定数据库函数扩展) + // 4.1 在内存中按 Day/Week/Month 聚合(避免依赖特定数据库函数扩展) var trend = trendRaw .GroupBy(x => GetTrendBucket(x.PeriodStart, normalizedGroupBy)) .Select(g => new TenantBillingTrendDataPoint @@ -371,7 +371,7 @@ public sealed class TenantBillingRepository(TakeoutAppDbContext context) : ITena var dayOfWeek = (int)date.DayOfWeek; // Sunday=0, Monday=1, ... var daysSinceMonday = (dayOfWeek + 6) % 7; - // 2. (空行后) 回退到周一 00:00:00(UTC) + // 2. 回退到周一 00:00:00(UTC) var monday = date.AddDays(-daysSinceMonday); return new DateTime(monday.Year, monday.Month, monday.Day, 0, 0, 0, DateTimeKind.Utc); } diff --git a/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Persistence/TakeoutAppDbContext.cs b/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Persistence/TakeoutAppDbContext.cs index 173cccb..77d76be 100644 --- a/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Persistence/TakeoutAppDbContext.cs +++ b/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Persistence/TakeoutAppDbContext.cs @@ -698,8 +698,8 @@ public sealed class TakeoutAppDbContext( .HasComment("发布状态:0=草稿,1=已发布。"); // 1. 解决 EF Core 默认值哨兵问题:当我们希望插入 false/0 时,若数据库配置了 default 且 EF 认为该值是“未设置”,会导致 insert 省略列,最终落库为默认值。 - // 2. (空行后) 发布状态使用 -1 作为哨兵,避免 Draft=0 被误判为“未设置”而触发数据库默认值(发布/草稿切换必须可控)。 - // 3. (空行后) 将布尔开关哨兵值设置为数据库默认值:true 作为哨兵,false 才会被显式写入,从而保证“可见性/可售开关”在新增时可正确落库。 + // 2. 发布状态使用 -1 作为哨兵,避免 Draft=0 被误判为“未设置”而触发数据库默认值(发布/草稿切换必须可控)。 + // 3. 将布尔开关哨兵值设置为数据库默认值:true 作为哨兵,false 才会被显式写入,从而保证“可见性/可售开关”在新增时可正确落库。 builder.Property(x => x.IsPublicVisible) .HasDefaultValue(true) .HasSentinel(true) @@ -709,7 +709,7 @@ public sealed class TakeoutAppDbContext( .HasSentinel(true) .HasComment("是否允许新租户购买/选择(仅影响新购)。"); - // 4. (空行后) 展示配置:推荐标识与标签(用于套餐展示页/对比页) + // 4. 展示配置:推荐标识与标签(用于套餐展示页/对比页) builder.Property(x => x.IsRecommended) .HasDefaultValue(false) .HasComment("是否推荐展示(运营推荐标识)。"); diff --git a/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Repositories/EfMerchantRepository.cs b/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Repositories/EfMerchantRepository.cs index 3c50cd5..1a2454f 100644 --- a/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Repositories/EfMerchantRepository.cs +++ b/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Repositories/EfMerchantRepository.cs @@ -178,7 +178,7 @@ public sealed class EfMerchantRepository(TakeoutAppDbContext context, TakeoutLog // 1. 保存业务库变更 await context.SaveChangesAsync(cancellationToken); - // 2. (空行后) 保存日志库变更 + // 2. 保存日志库变更 await logsContext.SaveChangesAsync(cancellationToken); } diff --git a/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Repositories/EfSubscriptionRepository.cs b/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Repositories/EfSubscriptionRepository.cs index 9a1802b..5eca26d 100644 --- a/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Repositories/EfSubscriptionRepository.cs +++ b/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Repositories/EfSubscriptionRepository.cs @@ -406,7 +406,7 @@ public sealed class EfSubscriptionRepository(TakeoutAppDbContext dbContext, Take // 1. 保存业务库变更 await dbContext.SaveChangesAsync(cancellationToken); - // 2. (空行后) 保存日志库变更 + // 2. 保存日志库变更 await logsContext.SaveChangesAsync(cancellationToken); } } diff --git a/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Repositories/EfTenantRepository.cs b/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Repositories/EfTenantRepository.cs index ecfa185..8e5c4f9 100644 --- a/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Repositories/EfTenantRepository.cs +++ b/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Repositories/EfTenantRepository.cs @@ -105,21 +105,21 @@ public sealed class EfTenantRepository(TakeoutAppDbContext context, TakeoutLogsD query = query.Where(x => EF.Functions.ILike(x.Name, $"%{normalizedName}%")); } - // 4. (空行后) 按联系人过滤(模糊匹配) + // 4. 按联系人过滤(模糊匹配) if (!string.IsNullOrWhiteSpace(contactName)) { var normalizedContactName = contactName.Trim(); query = query.Where(x => EF.Functions.ILike(x.ContactName ?? string.Empty, $"%{normalizedContactName}%")); } - // 5. (空行后) 按联系电话过滤(模糊匹配) + // 5. 按联系电话过滤(模糊匹配) if (!string.IsNullOrWhiteSpace(contactPhone)) { var normalizedContactPhone = contactPhone.Trim(); query = query.Where(x => EF.Functions.ILike(x.ContactPhone ?? string.Empty, $"%{normalizedContactPhone}%")); } - // 6. (空行后) 兼容关键字查询:名称/编码/联系人/电话 + // 6. 兼容关键字查询:名称/编码/联系人/电话 if (!string.IsNullOrWhiteSpace(keyword)) { var normalizedKeyword = keyword.Trim(); @@ -130,10 +130,10 @@ public sealed class EfTenantRepository(TakeoutAppDbContext context, TakeoutLogsD EF.Functions.ILike(x.ContactPhone ?? string.Empty, $"%{normalizedKeyword}%")); } - // 7. (空行后) 先统计总数,再按创建时间倒序分页 + // 7. 先统计总数,再按创建时间倒序分页 var total = await query.CountAsync(cancellationToken); - // 8. (空行后) 查询当前页数据 + // 8. 查询当前页数据 var items = await query .OrderByDescending(x => x.CreatedAt) .Skip((page - 1) * pageSize) @@ -169,18 +169,18 @@ public sealed class EfTenantRepository(TakeoutAppDbContext context, TakeoutLogsD // 1. 标准化名称 var normalized = name.Trim(); - // 2. (空行后) 构建查询(名称使用 ILike 做不区分大小写的等值匹配) + // 2. 构建查询(名称使用 ILike 做不区分大小写的等值匹配) var query = context.Tenants .AsNoTracking() .Where(x => EF.Functions.ILike(x.Name, normalized)); - // 3. (空行后) 更新场景排除自身 + // 3. 更新场景排除自身 if (excludeTenantId.HasValue) { query = query.Where(x => x.Id != excludeTenantId.Value); } - // 4. (空行后) 判断是否存在 + // 4. 判断是否存在 return query.AnyAsync(cancellationToken); } @@ -282,7 +282,7 @@ public sealed class EfTenantRepository(TakeoutAppDbContext context, TakeoutLogsD await context.TenantReviewClaims.AddAsync(claim, cancellationToken); await context.SaveChangesAsync(cancellationToken); - // 2. (空行后) 写入审计日志 + // 2. 写入审计日志 await logsContext.TenantAuditLogs.AddAsync(auditLog, cancellationToken); await logsContext.SaveChangesAsync(cancellationToken); return true; @@ -292,7 +292,7 @@ public sealed class EfTenantRepository(TakeoutAppDbContext context, TakeoutLogsD // 1. 释放实体跟踪避免重复写入 context.Entry(claim).State = EntityState.Detached; - // 2. (空行后) 返回抢占失败 + // 2. 返回抢占失败 return false; } } @@ -398,7 +398,7 @@ public sealed class EfTenantRepository(TakeoutAppDbContext context, TakeoutLogsD // 1. 保存业务库变更 await context.SaveChangesAsync(cancellationToken); - // 2. (空行后) 保存日志库变更 + // 2. 保存日志库变更 await logsContext.SaveChangesAsync(cancellationToken); } } diff --git a/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Services/BillingDomainService.cs b/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Services/BillingDomainService.cs index 73b019e..76da8b6 100644 --- a/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Services/BillingDomainService.cs +++ b/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Services/BillingDomainService.cs @@ -33,14 +33,14 @@ public sealed class BillingDomainService( throw new InvalidOperationException("该订阅周期的账单已存在。"); } - // 2. (空行后) 查询套餐价格信息 + // 2. 查询套餐价格信息 var package = await tenantPackageRepository.FindByIdAsync(subscription.TenantPackageId, cancellationToken); if (package is null) { throw new InvalidOperationException("订阅未关联有效套餐,无法生成账单。"); } - // 3. (空行后) 选择价格(简化规则:优先按年/按月) + // 3. 选择价格(简化规则:优先按年/按月) var days = (subscription.EffectiveTo - subscription.EffectiveFrom).TotalDays; var amountDue = days >= 300 ? package.YearlyPrice : package.MonthlyPrice; if (!amountDue.HasValue) @@ -48,7 +48,7 @@ public sealed class BillingDomainService( throw new InvalidOperationException("套餐价格未配置,无法生成账单。"); } - // 4. (空行后) 生成账单明细 + // 4. 生成账单明细 var lineItems = new List { BillingLineItem.Create( @@ -58,7 +58,7 @@ public sealed class BillingDomainService( unitPrice: amountDue.Value) }; - // 5. (空行后) 构建账单实体 + // 5. 构建账单实体 var now = DateTime.UtcNow; return new TenantBillingStatement { @@ -98,7 +98,7 @@ public sealed class BillingDomainService( // 1. 计算金额 var amountDue = quotaPackage.Price * quantity; - // 2. (空行后) 生成账单明细 + // 2. 生成账单明细 var lineItems = new List { BillingLineItem.Create( @@ -108,7 +108,7 @@ public sealed class BillingDomainService( unitPrice: quotaPackage.Price) }; - // 3. (空行后) 构建账单实体 + // 3. 构建账单实体 var now = DateTime.UtcNow; var billing = new TenantBillingStatement { @@ -139,7 +139,7 @@ public sealed class BillingDomainService( // 1. 账单号格式:BILL-{yyyyMMdd}-{序号} var date = DateTime.UtcNow.ToString("yyyyMMdd", CultureInfo.InvariantCulture); - // 2. (空行后) 使用雪花 ID 作为全局递增序号,确保分布式唯一 + // 2. 使用雪花 ID 作为全局递增序号,确保分布式唯一 var sequence = idGenerator.NextId(); return $"BILL-{date}-{sequence}"; } @@ -154,7 +154,7 @@ public sealed class BillingDomainService( return 0; } - // 2. (空行后) 批量标记逾期(防御性:再次判断 Pending) + // 2. 批量标记逾期(防御性:再次判断 Pending) var processedAt = DateTime.UtcNow; var updated = 0; foreach (var billing in overdueBillings) @@ -172,7 +172,7 @@ public sealed class BillingDomainService( updated++; } - // 3. (空行后) 持久化 + // 3. 持久化 if (updated > 0) { await billingRepository.SaveChangesAsync(cancellationToken); diff --git a/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Services/BillingExportService.cs b/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Services/BillingExportService.cs index 9578c97..0fb69e2 100644 --- a/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Services/BillingExportService.cs +++ b/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Services/BillingExportService.cs @@ -33,7 +33,7 @@ public sealed class BillingExportService : IBillingExportService using var workbook = new XLWorkbook(); var worksheet = workbook.Worksheets.Add("Billings"); - // 2. (空行后) 写入表头 + // 2. 写入表头 var headers = new[] { "Id", "TenantId", "StatementNo", "BillingType", "Status", @@ -46,7 +46,7 @@ public sealed class BillingExportService : IBillingExportService worksheet.Cell(1, i + 1).Value = headers[i]; } - // 3. (空行后) 写入数据行 + // 3. 写入数据行 for (var rowIndex = 0; rowIndex < billings.Count; rowIndex++) { cancellationToken.ThrowIfCancellationRequested(); @@ -73,7 +73,7 @@ public sealed class BillingExportService : IBillingExportService worksheet.Cell(r, 16).Value = billing.LineItemsJson ?? string.Empty; } - // 4. (空行后) 自动调整列宽并输出 + // 4. 自动调整列宽并输出 worksheet.Columns().AdjustToContents(); using var stream = new MemoryStream(); @@ -102,7 +102,7 @@ public sealed class BillingExportService : IBillingExportService // 2. 标题 column.Item().Text("Billings Export").FontSize(16).SemiBold(); - // 3. (空行后) 逐条输出 + // 3. 逐条输出 for (var i = 0; i < billings.Count; i++) { cancellationToken.ThrowIfCancellationRequested(); @@ -129,7 +129,7 @@ public sealed class BillingExportService : IBillingExportService }); }); - // 4. (空行后) 输出字节 + // 4. 输出字节 var bytes = document.GeneratePdf(); return Task.FromResult(bytes); } @@ -169,7 +169,7 @@ public sealed class BillingExportService : IBillingExportService csv.WriteField("LineItemsJson"); await csv.NextRecordAsync(); - // 3. (空行后) 写入数据行 + // 3. 写入数据行 foreach (var b in billings) { cancellationToken.ThrowIfCancellationRequested(); @@ -196,7 +196,7 @@ public sealed class BillingExportService : IBillingExportService await csv.NextRecordAsync(); } - // 4. (空行后) Flush 并返回字节 + // 4. Flush 并返回字节 await writer.FlushAsync(cancellationToken); return stream.ToArray(); } diff --git a/src/Infrastructure/TakeoutSaaS.Infrastructure/Identity/Services/RedisAdminPasswordResetTokenStore.cs b/src/Infrastructure/TakeoutSaaS.Infrastructure/Identity/Services/RedisAdminPasswordResetTokenStore.cs index 187278e..fad8217 100644 --- a/src/Infrastructure/TakeoutSaaS.Infrastructure/Identity/Services/RedisAdminPasswordResetTokenStore.cs +++ b/src/Infrastructure/TakeoutSaaS.Infrastructure/Identity/Services/RedisAdminPasswordResetTokenStore.cs @@ -22,13 +22,13 @@ public sealed class RedisAdminPasswordResetTokenStore( // 1. 生成 URL 安全的随机令牌 var token = GenerateUrlSafeToken(48); - // 2. (空行后) 写入缓存(Value:userId) + // 2. 写入缓存(Value:userId) await cache.SetStringAsync(BuildKey(token), userId.ToString(), new DistributedCacheEntryOptions { AbsoluteExpiration = expiresAt }, cancellationToken); - // 3. (空行后) 返回令牌 + // 3. 返回令牌 return token; } @@ -43,10 +43,10 @@ public sealed class RedisAdminPasswordResetTokenStore( return null; } - // 2. (空行后) 删除缓存(一次性令牌) + // 2. 删除缓存(一次性令牌) await cache.RemoveAsync(key, cancellationToken); - // 3. (空行后) 解析用户 ID + // 3. 解析用户 ID return long.TryParse(value, out var userId) ? userId : null; } diff --git a/src/Infrastructure/TakeoutSaaS.Infrastructure/Logs/Consumers/IdentityUserOperationLogConsumer.cs b/src/Infrastructure/TakeoutSaaS.Infrastructure/Logs/Consumers/IdentityUserOperationLogConsumer.cs index 12dcdea..0d92d0b 100644 --- a/src/Infrastructure/TakeoutSaaS.Infrastructure/Logs/Consumers/IdentityUserOperationLogConsumer.cs +++ b/src/Infrastructure/TakeoutSaaS.Infrastructure/Logs/Consumers/IdentityUserOperationLogConsumer.cs @@ -30,7 +30,7 @@ public sealed class IdentityUserOperationLogConsumer(TakeoutLogsDbContext logsCo return; } - // 2. (空行后) 构建日志实体与去重记录 + // 2. 构建日志实体与去重记录 var message = context.Message; var log = new OperationLog { @@ -50,7 +50,7 @@ public sealed class IdentityUserOperationLogConsumer(TakeoutLogsDbContext logsCo }); logsContext.OperationLogs.Add(log); - // 3. (空行后) 保存并处理并发去重冲突 + // 3. 保存并处理并发去重冲突 try { await logsContext.SaveChangesAsync(context.CancellationToken); diff --git a/src/Infrastructure/TakeoutSaaS.Infrastructure/Logs/Extensions/OperationLogOutboxServiceCollectionExtensions.cs b/src/Infrastructure/TakeoutSaaS.Infrastructure/Logs/Extensions/OperationLogOutboxServiceCollectionExtensions.cs index c4a0029..33b452d 100644 --- a/src/Infrastructure/TakeoutSaaS.Infrastructure/Logs/Extensions/OperationLogOutboxServiceCollectionExtensions.cs +++ b/src/Infrastructure/TakeoutSaaS.Infrastructure/Logs/Extensions/OperationLogOutboxServiceCollectionExtensions.cs @@ -27,7 +27,7 @@ public static class OperationLogOutboxServiceCollectionExtensions throw new InvalidOperationException("缺少 RabbitMQ 配置。"); } - // 2. (空行后) 注册 MassTransit 与 Outbox + // 2. 注册 MassTransit 与 Outbox services.AddMassTransit(configurator => { configurator.AddConsumer(); @@ -50,7 +50,7 @@ public static class OperationLogOutboxServiceCollectionExtensions cfg.ConfigureEndpoints(context); }); }); - // 3. (空行后) 返回服务集合 + // 3. 返回服务集合 return services; } } diff --git a/src/Modules/TakeoutSaaS.Module.Scheduler/Services/RecurringJobRegistrar.cs b/src/Modules/TakeoutSaaS.Module.Scheduler/Services/RecurringJobRegistrar.cs index 71c90f0..8d63691 100644 --- a/src/Modules/TakeoutSaaS.Module.Scheduler/Services/RecurringJobRegistrar.cs +++ b/src/Modules/TakeoutSaaS.Module.Scheduler/Services/RecurringJobRegistrar.cs @@ -22,7 +22,7 @@ public sealed class RecurringJobRegistrar( RecurringJob.AddOrUpdate("coupons.expire", job => job.ExecuteAsync(), "0 */1 * * *"); RecurringJob.AddOrUpdate("logs.cleanup", job => job.ExecuteAsync(), "0 3 * * *"); - // 2. (空行后) 订阅自动化任务(自动续费、续费提醒、到期进入宽限期) + // 2. 订阅自动化任务(自动续费、续费提醒、到期进入宽限期) var options = subscriptionAutomationOptions.CurrentValue; RecurringJob.AddOrUpdate( "subscriptions.auto-renewal", @@ -37,7 +37,7 @@ public sealed class RecurringJobRegistrar( job => job.ExecuteAsync(), $"0 {options.SubscriptionExpiryCheckExecuteHourUtc} * * *"); - // 3. (空行后) 账单自动化任务(逾期标记) + // 3. 账单自动化任务(逾期标记) var billingOptions = billingAutomationOptions.CurrentValue; RecurringJob.AddOrUpdate( "billings.overdue-process",