From 21e8ff5372e64047a09e2003773c9a71eb80e7fc Mon Sep 17 00:00:00 2001 From: zpc Date: Fri, 20 Feb 2026 20:29:34 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=E6=B8=85=E7=90=86=E9=81=97?= =?UTF-8?q?=E7=95=99=E5=AE=9E=E4=BD=93=E5=92=8C=E6=97=A0=E6=95=88=E4=BB=A3?= =?UTF-8?q?=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 删除无数据库表的实体: UserDetail, UserAddress, PaymentOrder, Admin, AdminLoginLog, AdminOperationLog, Picture, Delivery - 删除关联服务: AddressService, PaymentService, PaymentOrderService, PaymentRewardDispatcher, DefaultPaymentRewardHandler - 删除关联接口: IAddressService, IPaymentService, IPaymentOrderService, IPaymentRewardHandler, IPaymentRewardDispatcher - 删除关联控制器: AddressController - 删除关联DTO: AddressModels, CreatePaymentOrderRequest, PaymentOrderDto, PaymentOrderQueryRequest - 删除关联测试: PaymentOrderServicePropertyTests, PaymentRewardDispatcherPropertyTests - 修复实体字段映射: User, UserLoginLog, UserRefreshToken, Config, OrderNotify - 更新 NotifyController 移除 IPaymentOrderService 依赖 - 更新 ServiceModule 移除已删除服务的DI注册 - 更新 MiAssessmentDbContext 移除已删除实体的DbSet和OnModelCreating配置 --- .../Services/DashboardService.cs | 2 +- .../Controllers/AddressController.cs | 350 --------- .../Controllers/NotifyController.cs | 45 -- .../Interfaces/IAddressService.cs | 63 -- .../Interfaces/IPaymentOrderService.cs | 72 -- .../Interfaces/IPaymentRewardDispatcher.cs | 38 - .../Interfaces/IPaymentRewardHandler.cs | 71 -- .../Interfaces/IPaymentService.cs | 25 - .../Services/AddressService.cs | 221 ------ .../MiAssessment.Core/Services/AuthService.cs | 32 +- .../Services/DefaultPaymentRewardHandler.cs | 85 --- .../Services/InviteService.cs | 16 +- .../Services/PaymentNotifyService.cs | 8 +- .../Services/PaymentOrderService.cs | 389 ---------- .../Services/PaymentRewardDispatcher.cs | 136 ---- .../Services/PaymentService.cs | 59 -- .../MiAssessment.Core/Services/UserService.cs | 26 +- .../Services/WechatPayService.cs | 4 +- .../Services/WechatPayV3Service.cs | 4 +- .../Services/WechatService.cs | 5 +- .../Modules/ServiceModule.cs | 47 +- .../Data/MiAssessmentDbContext.cs | 667 ++++-------------- .../src/MiAssessment.Model/Entities/Admin.cs | 70 -- .../Entities/AdminLoginLog.cs | 30 - .../Entities/AdminOperationLog.cs | 40 -- .../src/MiAssessment.Model/Entities/Config.cs | 49 +- .../MiAssessment.Model/Entities/Delivery.cs | 25 - .../Entities/OrderNotify.cs | 27 +- .../Entities/PaymentOrder.cs | 106 --- .../MiAssessment.Model/Entities/Picture.cs | 40 -- .../src/MiAssessment.Model/Entities/User.cs | 74 +- .../Entities/UserAddress.cs | 55 -- .../MiAssessment.Model/Entities/UserDetail.cs | 37 - .../Entities/UserLoginLog.cs | 61 +- .../Models/Address/AddressModels.cs | 115 --- .../Payment/CreatePaymentOrderRequest.cs | 47 -- .../Models/Payment/PaymentOrderDto.cs | 120 ---- .../Payment/PaymentOrderQueryRequest.cs | 45 -- .../Core/PaymentOrderServicePropertyTests.cs | 607 ---------------- .../PaymentRewardDispatcherPropertyTests.cs | 540 -------------- .../AuthServiceLoginRecordPropertyTests.cs | 52 +- 41 files changed, 317 insertions(+), 4188 deletions(-) delete mode 100644 server/MiAssessment/src/MiAssessment.Api/Controllers/AddressController.cs delete mode 100644 server/MiAssessment/src/MiAssessment.Core/Interfaces/IAddressService.cs delete mode 100644 server/MiAssessment/src/MiAssessment.Core/Interfaces/IPaymentOrderService.cs delete mode 100644 server/MiAssessment/src/MiAssessment.Core/Interfaces/IPaymentRewardDispatcher.cs delete mode 100644 server/MiAssessment/src/MiAssessment.Core/Interfaces/IPaymentRewardHandler.cs delete mode 100644 server/MiAssessment/src/MiAssessment.Core/Interfaces/IPaymentService.cs delete mode 100644 server/MiAssessment/src/MiAssessment.Core/Services/AddressService.cs delete mode 100644 server/MiAssessment/src/MiAssessment.Core/Services/DefaultPaymentRewardHandler.cs delete mode 100644 server/MiAssessment/src/MiAssessment.Core/Services/PaymentOrderService.cs delete mode 100644 server/MiAssessment/src/MiAssessment.Core/Services/PaymentRewardDispatcher.cs delete mode 100644 server/MiAssessment/src/MiAssessment.Core/Services/PaymentService.cs delete mode 100644 server/MiAssessment/src/MiAssessment.Model/Entities/Admin.cs delete mode 100644 server/MiAssessment/src/MiAssessment.Model/Entities/AdminLoginLog.cs delete mode 100644 server/MiAssessment/src/MiAssessment.Model/Entities/AdminOperationLog.cs delete mode 100644 server/MiAssessment/src/MiAssessment.Model/Entities/Delivery.cs delete mode 100644 server/MiAssessment/src/MiAssessment.Model/Entities/PaymentOrder.cs delete mode 100644 server/MiAssessment/src/MiAssessment.Model/Entities/Picture.cs delete mode 100644 server/MiAssessment/src/MiAssessment.Model/Entities/UserAddress.cs delete mode 100644 server/MiAssessment/src/MiAssessment.Model/Entities/UserDetail.cs delete mode 100644 server/MiAssessment/src/MiAssessment.Model/Models/Address/AddressModels.cs delete mode 100644 server/MiAssessment/src/MiAssessment.Model/Models/Payment/CreatePaymentOrderRequest.cs delete mode 100644 server/MiAssessment/src/MiAssessment.Model/Models/Payment/PaymentOrderDto.cs delete mode 100644 server/MiAssessment/src/MiAssessment.Model/Models/Payment/PaymentOrderQueryRequest.cs delete mode 100644 server/MiAssessment/tests/MiAssessment.Tests/Core/PaymentOrderServicePropertyTests.cs delete mode 100644 server/MiAssessment/tests/MiAssessment.Tests/Core/PaymentRewardDispatcherPropertyTests.cs diff --git a/server/MiAssessment/src/MiAssessment.Admin.Business/Services/DashboardService.cs b/server/MiAssessment/src/MiAssessment.Admin.Business/Services/DashboardService.cs index b2cdaf6..06db92b 100644 --- a/server/MiAssessment/src/MiAssessment.Admin.Business/Services/DashboardService.cs +++ b/server/MiAssessment/src/MiAssessment.Admin.Business/Services/DashboardService.cs @@ -36,7 +36,7 @@ public class DashboardService : IDashboardService // 今日注册用户数 var todayRegistrations = await _dbContext.Users - .CountAsync(u => u.CreatedAt >= today && u.CreatedAt < tomorrow); + .CountAsync(u => u.CreateTime >= today && u.CreateTime < tomorrow); // 总用户数 var totalUsers = await _dbContext.Users.CountAsync(); diff --git a/server/MiAssessment/src/MiAssessment.Api/Controllers/AddressController.cs b/server/MiAssessment/src/MiAssessment.Api/Controllers/AddressController.cs deleted file mode 100644 index 59fbd44..0000000 --- a/server/MiAssessment/src/MiAssessment.Api/Controllers/AddressController.cs +++ /dev/null @@ -1,350 +0,0 @@ -using System.Security.Claims; -using MiAssessment.Core.Interfaces; -using MiAssessment.Model.Base; -using MiAssessment.Model.Models.Address; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; - -namespace MiAssessment.Api.Controllers; - -/// -/// 地址控制器 - 处理用户收货地址相关功能 -/// -/// -/// 提供地址的增删改查、设置默认地址等功能 -/// Requirements: 1.1-1.7 -/// -[ApiController] -[Route("api")] -public class AddressController : ControllerBase -{ - private readonly IAddressService _addressService; - private readonly ILogger _logger; - - public AddressController(IAddressService addressService, ILogger logger) - { - _addressService = addressService; - _logger = logger; - } - - /// - /// 添加收货地址 - /// - /// - /// POST /api/addAddress - /// - /// 每位用户最多只能添加10条收货地址 - /// Requirements: 1.1 - /// - [HttpPost("addAddress")] - [Authorize] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - public async Task> AddAddress([FromBody] AddAddressRequest request) - { - var userId = GetCurrentUserId(); - if (userId == null) - { - return ApiResponse.Unauthorized(); - } - - // 参数验证 - if (string.IsNullOrWhiteSpace(request.ReceiverName)) - { - return ApiResponse.Fail("请输入收货人姓名"); - } - if (string.IsNullOrWhiteSpace(request.ReceiverPhone)) - { - return ApiResponse.Fail("请输入收货人电话"); - } - if (!IsValidMobile(request.ReceiverPhone)) - { - return ApiResponse.Fail("请输入正确的手机号"); - } - if (string.IsNullOrWhiteSpace(request.DetailedAddress)) - { - return ApiResponse.Fail("请输入详细地址"); - } - - try - { - var address = await _addressService.AddAddressAsync(userId.Value, request); - return ApiResponse.Success(address, "添加成功"); - } - catch (InvalidOperationException ex) - { - _logger.LogWarning("Add address failed: UserId={UserId}, Error={Error}", userId, ex.Message); - return ApiResponse.Fail(ex.Message); - } - catch (Exception ex) - { - _logger.LogError(ex, "Failed to add address: UserId={UserId}", userId); - return ApiResponse.Fail("添加失败"); - } - } - - /// - /// 更新收货地址 - /// - /// - /// POST /api/updateAddress - /// Requirements: 1.2 - /// - [HttpPost("updateAddress")] - [Authorize] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - public async Task> UpdateAddress([FromBody] UpdateAddressRequest request) - { - var userId = GetCurrentUserId(); - if (userId == null) - { - return ApiResponse.Unauthorized(); - } - - // 参数验证 - if (request.Id <= 0) - { - return ApiResponse.Fail("请选择要修改的地址"); - } - if (string.IsNullOrWhiteSpace(request.ReceiverName)) - { - return ApiResponse.Fail("请输入收货人姓名"); - } - if (string.IsNullOrWhiteSpace(request.ReceiverPhone)) - { - return ApiResponse.Fail("请输入收货人电话"); - } - if (!IsValidMobile(request.ReceiverPhone)) - { - return ApiResponse.Fail("请输入正确的手机号"); - } - if (string.IsNullOrWhiteSpace(request.DetailedAddress)) - { - return ApiResponse.Fail("请输入详细地址"); - } - - try - { - var address = await _addressService.UpdateAddressAsync(userId.Value, request); - return ApiResponse.Success(address, "修改成功"); - } - catch (InvalidOperationException ex) - { - _logger.LogWarning("Update address failed: UserId={UserId}, Error={Error}", userId, ex.Message); - return ApiResponse.Fail(ex.Message); - } - catch (Exception ex) - { - _logger.LogError(ex, "Failed to update address: UserId={UserId}", userId); - return ApiResponse.Fail("修改失败"); - } - } - - /// - /// 获取默认收货地址 - /// - /// - /// GET /api/getDefaultAddress - /// Requirements: 1.3 - /// - [HttpGet("getDefaultAddress")] - [Authorize] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - public async Task> GetDefaultAddress() - { - var userId = GetCurrentUserId(); - if (userId == null) - { - return ApiResponse.Unauthorized(); - } - - try - { - var address = await _addressService.GetDefaultAddressAsync(userId.Value); - return ApiResponse.Success(address, "获取成功"); - } - catch (Exception ex) - { - _logger.LogError(ex, "Failed to get default address: UserId={UserId}", userId); - return ApiResponse.Fail("获取失败"); - } - } - - - /// - /// 获取收货地址列表 - /// - /// - /// GET /api/getAddressList - /// Requirements: 1.4 - /// - [HttpGet("getAddressList")] - [Authorize] - [ProducesResponseType(typeof(ApiResponse>), StatusCodes.Status200OK)] - public async Task>> GetAddressList() - { - var userId = GetCurrentUserId(); - if (userId == null) - { - return ApiResponse>.Unauthorized(); - } - - try - { - var addresses = await _addressService.GetAddressListAsync(userId.Value); - return ApiResponse>.Success(addresses, "获取成功"); - } - catch (Exception ex) - { - _logger.LogError(ex, "Failed to get address list: UserId={UserId}", userId); - return ApiResponse>.Fail("获取失败"); - } - } - - /// - /// 删除收货地址 - /// - /// - /// POST /api/deleteAddress - /// Requirements: 1.5 - /// - [HttpPost("deleteAddress")] - [Authorize] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - public async Task DeleteAddress([FromBody] AddressIdRequest request) - { - var userId = GetCurrentUserId(); - if (userId == null) - { - return ApiResponse.Unauthorized(); - } - - if (request.Id <= 0) - { - return ApiResponse.Fail("请选择要删除的地址"); - } - - try - { - await _addressService.DeleteAddressAsync(userId.Value, request.Id); - return ApiResponse.Success("删除成功"); - } - catch (InvalidOperationException ex) - { - _logger.LogWarning("Delete address failed: UserId={UserId}, Error={Error}", userId, ex.Message); - return ApiResponse.Fail(ex.Message); - } - catch (Exception ex) - { - _logger.LogError(ex, "Failed to delete address: UserId={UserId}", userId); - return ApiResponse.Fail("删除失败"); - } - } - - /// - /// 设置默认收货地址 - /// - /// - /// POST /api/setDefaultAddress - /// Requirements: 1.6 - /// - [HttpPost("setDefaultAddress")] - [Authorize] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - public async Task SetDefaultAddress([FromBody] AddressIdRequest request) - { - var userId = GetCurrentUserId(); - if (userId == null) - { - return ApiResponse.Unauthorized(); - } - - if (request.Id <= 0) - { - return ApiResponse.Fail("请选择要设为默认的地址"); - } - - try - { - await _addressService.SetDefaultAddressAsync(userId.Value, request.Id); - return ApiResponse.Success("设置成功"); - } - catch (InvalidOperationException ex) - { - _logger.LogWarning("Set default address failed: UserId={UserId}, Error={Error}", userId, ex.Message); - return ApiResponse.Fail(ex.Message); - } - catch (Exception ex) - { - _logger.LogError(ex, "Failed to set default address: UserId={UserId}", userId); - return ApiResponse.Fail("设置失败"); - } - } - - /// - /// 获取地址详情 - /// - /// - /// GET /api/getAddressDetail - /// Requirements: 1.7 - /// - [HttpGet("getAddressDetail")] - [Authorize] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - public async Task> GetAddressDetail([FromQuery] int id) - { - var userId = GetCurrentUserId(); - if (userId == null) - { - return ApiResponse.Unauthorized(); - } - - if (id <= 0) - { - return ApiResponse.Fail("请选择要查看的地址"); - } - - try - { - var address = await _addressService.GetAddressDetailAsync(userId.Value, id); - if (address == null) - { - return ApiResponse.Fail("地址不存在"); - } - return ApiResponse.Success(address, "获取成功"); - } - catch (Exception ex) - { - _logger.LogError(ex, "Failed to get address detail: UserId={UserId}, AddressId={AddressId}", userId, id); - return ApiResponse.Fail("获取失败"); - } - } - - #region Private Helper Methods - - /// - /// 获取当前登录用户ID - /// - private int? GetCurrentUserId() - { - var userIdClaim = User.FindFirst(ClaimTypes.NameIdentifier); - if (userIdClaim == null || !int.TryParse(userIdClaim.Value, out var userId)) - { - return null; - } - return userId; - } - - /// - /// 验证手机号格式 - /// - private static bool IsValidMobile(string mobile) - { - if (string.IsNullOrWhiteSpace(mobile)) - return false; - - // 中国大陆手机号:11位数字,以1开头 - return mobile.Length == 11 && mobile.StartsWith("1") && mobile.All(char.IsDigit); - } - - #endregion -} diff --git a/server/MiAssessment/src/MiAssessment.Api/Controllers/NotifyController.cs b/server/MiAssessment/src/MiAssessment.Api/Controllers/NotifyController.cs index 9c39ca4..830ba70 100644 --- a/server/MiAssessment/src/MiAssessment.Api/Controllers/NotifyController.cs +++ b/server/MiAssessment/src/MiAssessment.Api/Controllers/NotifyController.cs @@ -13,16 +13,13 @@ namespace MiAssessment.Api.Controllers; public class NotifyController : ControllerBase { private readonly IPaymentNotifyService _paymentNotifyService; - private readonly IPaymentOrderService _paymentOrderService; private readonly ILogger _logger; public NotifyController( IPaymentNotifyService paymentNotifyService, - IPaymentOrderService paymentOrderService, ILogger logger) { _paymentNotifyService = paymentNotifyService; - _paymentOrderService = paymentOrderService; _logger = logger; } @@ -67,48 +64,6 @@ public class NotifyController : ControllerBase _logger.LogInformation("微信支付回调处理完成: Success={Success}, Message={Message}", result.Success, result.Message); - // 如果回调处理成功且有订单号和支付数据,调用 PaymentOrderService 处理支付成功 - if (result.Success && !string.IsNullOrEmpty(result.OrderNo) && result.NotifyData != null) - { - try - { - // 从回调数据中获取交易号和支付金额(分转元) - var transactionId = result.NotifyData.TransactionId; - var payAmount = result.NotifyData.TotalFee / 100m; - - _logger.LogInformation("开始处理支付订单: OrderNo={OrderNo}, TransactionId={TransactionId}, PayAmount={PayAmount}", - result.OrderNo, transactionId, payAmount); - - // 调用 PaymentOrderService 处理支付成功(更新 PaymentOrder 状态并触发奖励发放) - var paymentResult = await _paymentOrderService.HandlePaymentSuccessAsync( - result.OrderNo, - transactionId, - payAmount); - - if (paymentResult) - { - _logger.LogInformation("支付订单处理成功: OrderNo={OrderNo}", result.OrderNo); - - // 更新 OrderNotify 状态为处理成功 - await _paymentNotifyService.UpdateNotifyStatusAsync(result.OrderNo, 1, "处理成功"); - } - else - { - _logger.LogWarning("支付订单处理失败: OrderNo={OrderNo}", result.OrderNo); - - // 更新 OrderNotify 状态为处理失败 - await _paymentNotifyService.UpdateNotifyStatusAsync(result.OrderNo, 2, "支付订单处理失败"); - } - } - catch (Exception ex) - { - _logger.LogError(ex, "处理支付订单异常: OrderNo={OrderNo}", result.OrderNo); - - // 更新 OrderNotify 状态为处理失败 - await _paymentNotifyService.UpdateNotifyStatusAsync(result.OrderNo, 2, $"处理异常: {ex.Message}"); - } - } - // 根据回调版本返回对应格式的响应 if (!string.IsNullOrEmpty(result.JsonResponse)) { diff --git a/server/MiAssessment/src/MiAssessment.Core/Interfaces/IAddressService.cs b/server/MiAssessment/src/MiAssessment.Core/Interfaces/IAddressService.cs deleted file mode 100644 index 1c32b31..0000000 --- a/server/MiAssessment/src/MiAssessment.Core/Interfaces/IAddressService.cs +++ /dev/null @@ -1,63 +0,0 @@ -using MiAssessment.Model.Models.Address; - -namespace MiAssessment.Core.Interfaces; - -/// -/// 地址服务接口 -/// -public interface IAddressService -{ - /// - /// 添加收货地址 - /// - /// 用户ID - /// 添加地址请求 - /// 新创建的地址 - Task AddAddressAsync(int userId, AddAddressRequest request); - - /// - /// 更新收货地址 - /// - /// 用户ID - /// 更新地址请求 - /// 更新后的地址 - Task UpdateAddressAsync(int userId, UpdateAddressRequest request); - - /// - /// 获取默认收货地址 - /// - /// 用户ID - /// 默认地址,如果没有则返回最新的一条 - Task GetDefaultAddressAsync(int userId); - - /// - /// 获取收货地址列表 - /// - /// 用户ID - /// 地址列表 - Task> GetAddressListAsync(int userId); - - /// - /// 删除收货地址 - /// - /// 用户ID - /// 地址ID - /// 是否删除成功 - Task DeleteAddressAsync(int userId, int addressId); - - /// - /// 设置默认收货地址 - /// - /// 用户ID - /// 地址ID - /// 是否设置成功 - Task SetDefaultAddressAsync(int userId, int addressId); - - /// - /// 获取地址详情 - /// - /// 用户ID - /// 地址ID - /// 地址详情 - Task GetAddressDetailAsync(int userId, int addressId); -} diff --git a/server/MiAssessment/src/MiAssessment.Core/Interfaces/IPaymentOrderService.cs b/server/MiAssessment/src/MiAssessment.Core/Interfaces/IPaymentOrderService.cs deleted file mode 100644 index bc22a9f..0000000 --- a/server/MiAssessment/src/MiAssessment.Core/Interfaces/IPaymentOrderService.cs +++ /dev/null @@ -1,72 +0,0 @@ -using MiAssessment.Model.Entities; -using MiAssessment.Model.Models; -using MiAssessment.Model.Models.Payment; - -namespace MiAssessment.Core.Interfaces; - -/// -/// 通用支付订单服务接口 -/// -public interface IPaymentOrderService -{ - /// - /// 创建支付订单 - /// - /// 创建订单请求 - /// 创建的支付订单 - Task CreateOrderAsync(CreatePaymentOrderRequest request); - - /// - /// 根据订单号获取订单详情 - /// - /// 订单号 - /// 支付订单,如果不存在则返回null - Task GetOrderByNoAsync(string orderNo); - - /// - /// 根据订单ID获取订单详情 - /// - /// 订单ID - /// 支付订单,如果不存在则返回null - Task GetOrderByIdAsync(int orderId); - - /// - /// 处理支付成功 - /// - /// 订单号 - /// 第三方交易号 - /// 实付金额 - /// 是否处理成功 - Task HandlePaymentSuccessAsync(string orderNo, string transactionId, decimal payAmount); - - /// - /// 处理奖励发放 - /// - /// 订单号 - /// 是否处理成功 - Task ProcessRewardAsync(string orderNo); - - /// - /// 获取用户订单列表 - /// - /// 用户ID - /// 查询请求 - /// 分页订单列表 - Task> GetUserOrdersAsync(int userId, PaymentOrderQueryRequest request); - - /// - /// 取消订单 - /// - /// 订单号 - /// 用户ID(用于验证权限) - /// 是否取消成功 - Task CancelOrderAsync(string orderNo, int userId); - - /// - /// 更新订单状态 - /// - /// 订单号 - /// 新状态 - /// 是否更新成功 - Task UpdateOrderStatusAsync(string orderNo, byte status); -} diff --git a/server/MiAssessment/src/MiAssessment.Core/Interfaces/IPaymentRewardDispatcher.cs b/server/MiAssessment/src/MiAssessment.Core/Interfaces/IPaymentRewardDispatcher.cs deleted file mode 100644 index dd7bb41..0000000 --- a/server/MiAssessment/src/MiAssessment.Core/Interfaces/IPaymentRewardDispatcher.cs +++ /dev/null @@ -1,38 +0,0 @@ -using MiAssessment.Model.Entities; - -namespace MiAssessment.Core.Interfaces; - -/// -/// 支付奖励分发器接口 -/// 负责根据订单类型查找并调用对应的奖励处理器 -/// -public interface IPaymentRewardDispatcher -{ - /// - /// 根据订单类型获取对应的奖励处理器 - /// - /// 订单类型 - /// 奖励处理器,如果未找到则返回 null - IPaymentRewardHandler? GetHandler(string orderType); - - /// - /// 检查是否存在指定订单类型的处理器 - /// - /// 订单类型 - /// 是否存在处理器 - bool HasHandler(string orderType); - - /// - /// 获取所有已注册的订单类型 - /// - /// 已注册的订单类型列表 - IReadOnlyCollection GetRegisteredOrderTypes(); - - /// - /// 处理奖励发放 - /// 根据订单类型查找对应的处理器并执行奖励发放 - /// - /// 支付订单 - /// 奖励处理结果 - Task ProcessRewardAsync(PaymentOrder order); -} diff --git a/server/MiAssessment/src/MiAssessment.Core/Interfaces/IPaymentRewardHandler.cs b/server/MiAssessment/src/MiAssessment.Core/Interfaces/IPaymentRewardHandler.cs deleted file mode 100644 index ceef69c..0000000 --- a/server/MiAssessment/src/MiAssessment.Core/Interfaces/IPaymentRewardHandler.cs +++ /dev/null @@ -1,71 +0,0 @@ -using MiAssessment.Model.Entities; - -namespace MiAssessment.Core.Interfaces; - -/// -/// 支付奖励处理器接口 -/// 用于处理支付成功后的奖励发放逻辑 -/// -public interface IPaymentRewardHandler -{ - /// - /// 处理的订单类型 - /// - string OrderType { get; } - - /// - /// 处理奖励发放 - /// - /// 支付订单 - /// 奖励处理结果 - Task ProcessRewardAsync(PaymentOrder order); -} - -/// -/// 奖励处理结果 -/// -public class RewardResult -{ - /// - /// 是否成功 - /// - public bool Success { get; set; } - - /// - /// 消息(成功时为空,失败时为错误原因) - /// - public string? Message { get; set; } - - /// - /// 奖励数据(JSON格式) - /// - public string? RewardData { get; set; } - - /// - /// 创建成功结果 - /// - /// 奖励数据 - /// 成功结果 - public static RewardResult Ok(string? rewardData = null) - { - return new RewardResult - { - Success = true, - RewardData = rewardData - }; - } - - /// - /// 创建失败结果 - /// - /// 错误消息 - /// 失败结果 - public static RewardResult Fail(string message) - { - return new RewardResult - { - Success = false, - Message = message - }; - } -} diff --git a/server/MiAssessment/src/MiAssessment.Core/Interfaces/IPaymentService.cs b/server/MiAssessment/src/MiAssessment.Core/Interfaces/IPaymentService.cs deleted file mode 100644 index b1f2753..0000000 --- a/server/MiAssessment/src/MiAssessment.Core/Interfaces/IPaymentService.cs +++ /dev/null @@ -1,25 +0,0 @@ -using MiAssessment.Model.Models.Payment; - -namespace MiAssessment.Core.Interfaces; - -/// -/// 支付服务接口 -/// 提供基础的支付相关功能,具体业务逻辑由业务模块扩展 -/// -public interface IPaymentService -{ - /// - /// 验证用户余额是否充足 - /// - /// 用户ID - /// 需要的金额 - /// 是否充足 - Task ValidateBalanceAsync(int userId, decimal amount); - - /// - /// 获取用户余额 - /// - /// 用户ID - /// 用户余额 - Task GetUserBalanceAsync(int userId); -} diff --git a/server/MiAssessment/src/MiAssessment.Core/Services/AddressService.cs b/server/MiAssessment/src/MiAssessment.Core/Services/AddressService.cs deleted file mode 100644 index fd42f0a..0000000 --- a/server/MiAssessment/src/MiAssessment.Core/Services/AddressService.cs +++ /dev/null @@ -1,221 +0,0 @@ -using MiAssessment.Core.Interfaces; -using MiAssessment.Model.Data; -using MiAssessment.Model.Entities; -using MiAssessment.Model.Models.Address; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Logging; - -namespace MiAssessment.Core.Services; - -/// -/// 地址服务实现 -/// -public class AddressService : IAddressService -{ - private readonly MiAssessmentDbContext _dbContext; - private readonly ILogger _logger; - private const int MaxAddressCount = 10; - - public AddressService(MiAssessmentDbContext dbContext, ILogger logger) - { - _dbContext = dbContext; - _logger = logger; - } - - /// - public async Task AddAddressAsync(int userId, AddAddressRequest request) - { - // 检查地址数量限制 - var addressCount = await _dbContext.UserAddresses - .CountAsync(a => a.UserId == userId && a.IsDeleted == 0); - - if (addressCount >= MaxAddressCount) - { - throw new InvalidOperationException("最多只能添加10个收货地址"); - } - - var now = DateTime.Now; - var address = new UserAddress - { - UserId = userId, - ReceiverName = request.ReceiverName, - ReceiverPhone = request.ReceiverPhone, - DetailedAddress = request.DetailedAddress, - IsDefault = (byte)(request.IsDefault == 1 ? 1 : 0), - IsDeleted = 0, - CreatedAt = now, - UpdatedAt = now - }; - - // 如果设置为默认地址,将其他地址设为非默认 - if (address.IsDefault == 1) - { - await SetOtherAddressNotDefaultAsync(userId); - } - - // 如果是第一个地址,自动设为默认 - if (addressCount == 0) - { - address.IsDefault = 1; - } - - _dbContext.UserAddresses.Add(address); - await _dbContext.SaveChangesAsync(); - - _logger.LogInformation("Address added: UserId={UserId}, AddressId={AddressId}", userId, address.Id); - return MapToDto(address); - } - - - /// - public async Task UpdateAddressAsync(int userId, UpdateAddressRequest request) - { - var address = await _dbContext.UserAddresses - .FirstOrDefaultAsync(a => a.Id == request.Id && a.UserId == userId && a.IsDeleted == 0); - - if (address == null) - { - throw new InvalidOperationException("地址不存在"); - } - - address.ReceiverName = request.ReceiverName; - address.ReceiverPhone = request.ReceiverPhone; - address.DetailedAddress = request.DetailedAddress; - address.UpdatedAt = DateTime.Now; - - // 处理默认地址设置 - if (request.IsDefault.HasValue && request.IsDefault.Value == 1 && address.IsDefault != 1) - { - await SetOtherAddressNotDefaultAsync(userId, address.Id); - address.IsDefault = 1; - } - - await _dbContext.SaveChangesAsync(); - - _logger.LogInformation("Address updated: UserId={UserId}, AddressId={AddressId}", userId, address.Id); - return MapToDto(address); - } - - /// - public async Task GetDefaultAddressAsync(int userId) - { - // 先查找默认地址 - var address = await _dbContext.UserAddresses - .FirstOrDefaultAsync(a => a.UserId == userId && a.IsDefault == 1 && a.IsDeleted == 0); - - // 如果没有默认地址,返回最新添加的一条 - if (address == null) - { - address = await _dbContext.UserAddresses - .Where(a => a.UserId == userId && a.IsDeleted == 0) - .OrderByDescending(a => a.Id) - .FirstOrDefaultAsync(); - } - - return address != null ? MapToDto(address) : null; - } - - /// - public async Task> GetAddressListAsync(int userId) - { - var addresses = await _dbContext.UserAddresses - .Where(a => a.UserId == userId && a.IsDeleted == 0) - .OrderByDescending(a => a.IsDefault) - .ThenByDescending(a => a.Id) - .ToListAsync(); - - return addresses.Select(MapToDto).ToList(); - } - - /// - public async Task DeleteAddressAsync(int userId, int addressId) - { - var address = await _dbContext.UserAddresses - .FirstOrDefaultAsync(a => a.Id == addressId && a.UserId == userId && a.IsDeleted == 0); - - if (address == null) - { - throw new InvalidOperationException("地址不存在"); - } - - // 软删除 - address.IsDeleted = 1; - address.UpdatedAt = DateTime.Now; - await _dbContext.SaveChangesAsync(); - - _logger.LogInformation("Address deleted: UserId={UserId}, AddressId={AddressId}", userId, addressId); - return true; - } - - /// - public async Task SetDefaultAddressAsync(int userId, int addressId) - { - var address = await _dbContext.UserAddresses - .FirstOrDefaultAsync(a => a.Id == addressId && a.UserId == userId && a.IsDeleted == 0); - - if (address == null) - { - throw new InvalidOperationException("地址不存在"); - } - - // 已经是默认地址 - if (address.IsDefault == 1) - { - return true; - } - - // 将其他地址设为非默认 - await SetOtherAddressNotDefaultAsync(userId); - - // 设置当前地址为默认 - address.IsDefault = 1; - address.UpdatedAt = DateTime.Now; - await _dbContext.SaveChangesAsync(); - - _logger.LogInformation("Default address set: UserId={UserId}, AddressId={AddressId}", userId, addressId); - return true; - } - - /// - public async Task GetAddressDetailAsync(int userId, int addressId) - { - var address = await _dbContext.UserAddresses - .FirstOrDefaultAsync(a => a.Id == addressId && a.UserId == userId && a.IsDeleted == 0); - - return address != null ? MapToDto(address) : null; - } - - /// - /// 将其他地址设为非默认 - /// - private async Task SetOtherAddressNotDefaultAsync(int userId, int? exceptId = null) - { - var query = _dbContext.UserAddresses - .Where(a => a.UserId == userId && a.IsDefault == 1 && a.IsDeleted == 0); - - if (exceptId.HasValue) - { - query = query.Where(a => a.Id != exceptId.Value); - } - - await query.ExecuteUpdateAsync(s => s.SetProperty(a => a.IsDefault, (byte)0)); - } - - /// - /// 将实体映射为DTO - /// - private static AddressDto MapToDto(UserAddress address) - { - return new AddressDto - { - Id = address.Id, - UserId = address.UserId, - ReceiverName = address.ReceiverName, - ReceiverPhone = address.ReceiverPhone, - DetailedAddress = address.DetailedAddress, - IsDefault = address.IsDefault ?? 0, - CreateTime = address.CreatedAt.ToString("yyyy-MM-dd HH:mm:ss"), - UpdateTime = address.UpdatedAt.ToString("yyyy-MM-dd HH:mm:ss") - }; - } -} diff --git a/server/MiAssessment/src/MiAssessment.Core/Services/AuthService.cs b/server/MiAssessment/src/MiAssessment.Core/Services/AuthService.cs index 331b671..8085f17 100644 --- a/server/MiAssessment/src/MiAssessment.Core/Services/AuthService.cs +++ b/server/MiAssessment/src/MiAssessment.Core/Services/AuthService.cs @@ -392,37 +392,25 @@ public class AuthService : IAuthService // 获取客户端IP(这里使用空字符串作为占位符,实际IP应从Controller传入) var clientIp = deviceInfo ?? string.Empty; - // 6.2 解析IP地址获取地理位置 - IpLocationResult? locationResult = null; - if (!string.IsNullOrWhiteSpace(clientIp)) - { - locationResult = await _ipLocationService.GetLocationAsync(clientIp); - } - - var now = DateTime.UtcNow; - var today = DateOnly.FromDateTime(now); + var now = DateTime.Now; // 6.1 记录登录日志 var loginLog = new UserLoginLog { UserId = userId, - LoginDate = today, - LoginTime = now, - LastLoginTime = now, - Device = device, - Ip = clientIp, - Location = locationResult?.Success == true - ? $"{locationResult.Province}{locationResult.City}" - : null, - Year = now.Year, - Month = now.Month, - Week = GetWeekOfYear(now) + LoginType = "wechat", + LoginIp = clientIp, + UserAgent = device, + Platform = "miniprogram", + Status = 1, + CreateTime = now }; await _dbContext.UserLoginLogs.AddAsync(loginLog); // 更新用户最后登录时间 user.LastLoginTime = now; + user.LastLoginIp = clientIp; _dbContext.Users.Update(user); await _dbContext.SaveChangesAsync(); @@ -434,7 +422,7 @@ public class AuthService : IAuthService { Uid = user.Uid, Nickname = user.Nickname, - Headimg = user.HeadImg + Headimg = user.Avatar }; } catch (Exception ex) @@ -802,7 +790,7 @@ public class AuthService : IAuthService { mobileUser.UnionId = currentUser.UnionId; } - mobileUser.UpdatedAt = DateTime.UtcNow; + mobileUser.UpdateTime = DateTime.Now; _dbContext.Users.Update(mobileUser); // 删除当前用户 diff --git a/server/MiAssessment/src/MiAssessment.Core/Services/DefaultPaymentRewardHandler.cs b/server/MiAssessment/src/MiAssessment.Core/Services/DefaultPaymentRewardHandler.cs deleted file mode 100644 index e69de78..0000000 --- a/server/MiAssessment/src/MiAssessment.Core/Services/DefaultPaymentRewardHandler.cs +++ /dev/null @@ -1,85 +0,0 @@ -using MiAssessment.Core.Interfaces; -using MiAssessment.Model.Entities; -using Microsoft.Extensions.Logging; - -namespace MiAssessment.Core.Services; - -/// -/// 默认支付奖励处理器示例 -/// 用于演示如何实现自定义奖励处理器 -/// -/// 使用方法: -/// 1. 创建一个新类实现 IPaymentRewardHandler 接口 -/// 2. 设置 OrderType 属性为要处理的订单类型 -/// 3. 在 ProcessRewardAsync 方法中实现奖励发放逻辑 -/// 4. 在 ServiceModule.cs 中注册处理器 -/// -/// 注册示例: -/// -/// builder.RegisterType<MyRewardHandler>() -/// .As<IPaymentRewardHandler>() -/// .InstancePerLifetimeScope(); -/// -/// -/// -/// 实现钻石充值奖励处理器示例: -/// -/// public class DiamondRechargeRewardHandler : IPaymentRewardHandler -/// { -/// public string OrderType => "diamond_recharge"; -/// -/// public async Task<RewardResult> ProcessRewardAsync(PaymentOrder order) -/// { -/// // 1. 解析业务数据 -/// var bizData = JsonSerializer.Deserialize<DiamondRechargeData>(order.BizData); -/// -/// // 2. 发放钻石 -/// await _userService.AddDiamondsAsync(order.UserId, bizData.DiamondAmount); -/// -/// // 3. 返回结果 -/// return RewardResult.Ok(JsonSerializer.Serialize(new { diamonds = bizData.DiamondAmount })); -/// } -/// } -/// -/// -public class DefaultPaymentRewardHandler : IPaymentRewardHandler -{ - private readonly ILogger _logger; - - /// - /// 处理的订单类型 - /// 默认处理器使用 "default" 类型,实际项目中应替换为具体的业务类型 - /// - public string OrderType => "default"; - - public DefaultPaymentRewardHandler(ILogger logger) - { - _logger = logger; - } - - /// - /// 处理奖励发放 - /// 默认实现仅记录日志,实际项目中应实现具体的奖励逻辑 - /// - /// 支付订单 - /// 奖励处理结果 - public Task ProcessRewardAsync(PaymentOrder order) - { - _logger.LogInformation( - "默认奖励处理器被调用: OrderNo={OrderNo}, UserId={UserId}, Amount={Amount}, BizData={BizData}", - order.OrderNo, - order.UserId, - order.Amount, - order.BizData); - - // 默认处理器直接返回成功 - // 实际项目中应根据 order.BizData 中的业务数据执行具体的奖励逻辑 - var rewardData = System.Text.Json.JsonSerializer.Serialize(new - { - message = "默认奖励处理完成", - processedAt = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") - }); - - return Task.FromResult(RewardResult.Ok(rewardData)); - } -} diff --git a/server/MiAssessment/src/MiAssessment.Core/Services/InviteService.cs b/server/MiAssessment/src/MiAssessment.Core/Services/InviteService.cs index 5dab55c..b1e3ec3 100644 --- a/server/MiAssessment/src/MiAssessment.Core/Services/InviteService.cs +++ b/server/MiAssessment/src/MiAssessment.Core/Services/InviteService.cs @@ -75,7 +75,7 @@ public class InviteService : IInviteService // 统计邀请人数(直属下级) var inviteCount = await _dbContext.Users .AsNoTracking() - .CountAsync(u => u.Pid == userId); + .CountAsync(u => u.ParentUserId == userId); // 计算待提现金额(可提现余额) var pendingAmount = user.Balance; @@ -166,23 +166,23 @@ public class InviteService : IInviteService // 查询直属下级用户(Pid = userId) var query = _dbContext.Users .AsNoTracking() - .Where(u => u.Pid == userId); + .Where(u => u.ParentUserId == userId); // 获取总数 var total = await query.CountAsync(); // 分页查询下级用户 var invitedUsers = await query - .OrderByDescending(u => u.CreatedAt) + .OrderByDescending(u => u.CreateTime) .Skip((page - 1) * pageSize) .Take(pageSize) .Select(u => new { u.Id, u.Nickname, - u.HeadImg, + u.Avatar, u.Uid, - u.CreatedAt + u.CreateTime }) .ToListAsync(); @@ -204,9 +204,9 @@ public class InviteService : IInviteService { UserId = u.Id, Nickname = u.Nickname ?? "未设置昵称", - Avatar = u.HeadImg ?? string.Empty, + Avatar = u.Avatar ?? string.Empty, Uid = u.Uid, - RegisterDate = u.CreatedAt.ToString("yyyy-MM-dd"), + RegisterDate = u.CreateTime.ToString("yyyy-MM-dd"), Commission = commissions.TryGetValue(u.Id, out var commission) ? commission : 0 }).ToList(); @@ -398,7 +398,7 @@ public class InviteService : IInviteService // 扣减用户余额 (Requirement 13.1) user.Balance = afterBalance; - user.UpdatedAt = DateTime.Now; + user.UpdateTime = DateTime.Now; // 保存更改 await _dbContext.SaveChangesAsync(); diff --git a/server/MiAssessment/src/MiAssessment.Core/Services/PaymentNotifyService.cs b/server/MiAssessment/src/MiAssessment.Core/Services/PaymentNotifyService.cs index 18caea1..95d1a9e 100644 --- a/server/MiAssessment/src/MiAssessment.Core/Services/PaymentNotifyService.cs +++ b/server/MiAssessment/src/MiAssessment.Core/Services/PaymentNotifyService.cs @@ -367,7 +367,7 @@ public class PaymentNotifyService : IPaymentNotifyService existingNotify.PayTime = DateTime.Now; existingNotify.PayAmount = notifyData.TotalFee / 100m; existingNotify.RawData = null; // 可选:存储原始XML - existingNotify.UpdatedAt = DateTime.Now; + existingNotify.UpdateTime = DateTime.Now; } else { @@ -383,8 +383,8 @@ public class PaymentNotifyService : IPaymentNotifyService Attach = notifyData.Attach, OpenId = notifyData.OpenId, RawData = null, - CreatedAt = DateTime.Now, - UpdatedAt = DateTime.Now + CreateTime = DateTime.Now, + UpdateTime = DateTime.Now }; _dbContext.OrderNotifies.Add(notify); @@ -412,7 +412,7 @@ public class PaymentNotifyService : IPaymentNotifyService { notify.Status = status; notify.ErrorMessage = message; - notify.UpdatedAt = DateTime.Now; + notify.UpdateTime = DateTime.Now; await _dbContext.SaveChangesAsync(); return true; } diff --git a/server/MiAssessment/src/MiAssessment.Core/Services/PaymentOrderService.cs b/server/MiAssessment/src/MiAssessment.Core/Services/PaymentOrderService.cs deleted file mode 100644 index a828a71..0000000 --- a/server/MiAssessment/src/MiAssessment.Core/Services/PaymentOrderService.cs +++ /dev/null @@ -1,389 +0,0 @@ -using MiAssessment.Core.Interfaces; -using MiAssessment.Model.Data; -using MiAssessment.Model.Entities; -using MiAssessment.Model.Models; -using MiAssessment.Model.Models.Payment; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Logging; - -namespace MiAssessment.Core.Services; - -/// -/// 通用支付订单服务实现 -/// -public class PaymentOrderService : IPaymentOrderService -{ - private readonly MiAssessmentDbContext _dbContext; - private readonly IEnumerable _rewardHandlers; - private readonly ILogger _logger; - - public PaymentOrderService( - MiAssessmentDbContext dbContext, - IEnumerable rewardHandlers, - ILogger logger) - { - _dbContext = dbContext; - _rewardHandlers = rewardHandlers; - _logger = logger; - } - - /// - public async Task CreateOrderAsync(CreatePaymentOrderRequest request) - { - if (request == null) - throw new ArgumentNullException(nameof(request)); - - if (request.UserId <= 0) - throw new ArgumentException("用户ID无效", nameof(request)); - - if (string.IsNullOrWhiteSpace(request.OrderType)) - throw new ArgumentException("订单类型不能为空", nameof(request)); - - if (request.Amount <= 0) - throw new ArgumentException("订单金额必须大于0", nameof(request)); - - // 生成唯一订单号 - var orderNo = GenerateOrderNo(); - - var order = new PaymentOrder - { - OrderNo = orderNo, - UserId = request.UserId, - OrderType = request.OrderType, - Title = request.Title ?? string.Empty, - Amount = request.Amount, - PayAmount = request.PayAmount ?? request.Amount, - PayMethod = request.PayMethod, - Status = 0, // 待支付 - BizId = request.BizId, - BizData = request.BizData, - RewardStatus = 0, // 未发放 - CreatedAt = DateTime.Now, - UpdatedAt = DateTime.Now - }; - - _dbContext.PaymentOrders.Add(order); - await _dbContext.SaveChangesAsync(); - - _logger.LogInformation("创建支付订单成功: OrderNo={OrderNo}, UserId={UserId}, OrderType={OrderType}, Amount={Amount}", - orderNo, request.UserId, request.OrderType, request.Amount); - - return order; - } - - /// - public async Task GetOrderByNoAsync(string orderNo) - { - if (string.IsNullOrWhiteSpace(orderNo)) - return null; - - return await _dbContext.PaymentOrders - .FirstOrDefaultAsync(o => o.OrderNo == orderNo); - } - - /// - public async Task GetOrderByIdAsync(int orderId) - { - if (orderId <= 0) - return null; - - return await _dbContext.PaymentOrders - .FirstOrDefaultAsync(o => o.Id == orderId); - } - - /// - public async Task HandlePaymentSuccessAsync(string orderNo, string transactionId, decimal payAmount) - { - if (string.IsNullOrWhiteSpace(orderNo)) - { - _logger.LogWarning("处理支付成功失败: 订单号为空"); - return false; - } - - var order = await GetOrderByNoAsync(orderNo); - if (order == null) - { - _logger.LogWarning("处理支付成功失败: 订单不存在, OrderNo={OrderNo}", orderNo); - return false; - } - - // 幂等性检查:如果订单已支付,直接返回成功 - if (order.Status == 1) - { - _logger.LogInformation("订单已支付,跳过重复处理: OrderNo={OrderNo}", orderNo); - return true; - } - - // 只有待支付状态的订单才能处理 - if (order.Status != 0) - { - _logger.LogWarning("订单状态不正确,无法处理支付成功: OrderNo={OrderNo}, Status={Status}", orderNo, order.Status); - return false; - } - - // 更新订单状态 - order.Status = 1; // 已支付 - order.PaidAt = DateTime.Now; - order.TransactionId = transactionId; - order.PayAmount = payAmount; - order.UpdatedAt = DateTime.Now; - - await _dbContext.SaveChangesAsync(); - - _logger.LogInformation("订单支付成功: OrderNo={OrderNo}, TransactionId={TransactionId}, PayAmount={PayAmount}", - orderNo, transactionId, payAmount); - - // 触发奖励发放 - await ProcessRewardAsync(orderNo); - - return true; - } - - /// - public async Task ProcessRewardAsync(string orderNo) - { - if (string.IsNullOrWhiteSpace(orderNo)) - { - _logger.LogWarning("处理奖励失败: 订单号为空"); - return false; - } - - var order = await GetOrderByNoAsync(orderNo); - if (order == null) - { - _logger.LogWarning("处理奖励失败: 订单不存在, OrderNo={OrderNo}", orderNo); - return false; - } - - // 只有已支付且未发放奖励的订单才能处理 - if (order.Status != 1) - { - _logger.LogWarning("订单未支付,无法发放奖励: OrderNo={OrderNo}, Status={Status}", orderNo, order.Status); - return false; - } - - if (order.RewardStatus == 1) - { - _logger.LogInformation("奖励已发放,跳过重复处理: OrderNo={OrderNo}", orderNo); - return true; - } - - // 查找对应的奖励处理器 - var handler = _rewardHandlers.FirstOrDefault(h => h.OrderType == order.OrderType); - if (handler == null) - { - _logger.LogInformation("未找到订单类型对应的奖励处理器: OrderNo={OrderNo}, OrderType={OrderType}", - orderNo, order.OrderType); - // 没有处理器不算失败,可能该订单类型不需要奖励 - return true; - } - - try - { - _logger.LogInformation("开始处理奖励: OrderNo={OrderNo}, OrderType={OrderType}, Handler={Handler}", - orderNo, order.OrderType, handler.GetType().Name); - - var result = await handler.ProcessRewardAsync(order); - - if (result.Success) - { - order.RewardStatus = 1; // 已发放 - order.RewardData = result.RewardData; - order.RewardAt = DateTime.Now; - order.UpdatedAt = DateTime.Now; - - await _dbContext.SaveChangesAsync(); - - _logger.LogInformation("奖励发放成功: OrderNo={OrderNo}, RewardData={RewardData}", - orderNo, result.RewardData); - - return true; - } - else - { - order.RewardStatus = 2; // 发放失败 - order.RewardData = result.Message; - order.UpdatedAt = DateTime.Now; - - await _dbContext.SaveChangesAsync(); - - _logger.LogWarning("奖励发放失败: OrderNo={OrderNo}, Message={Message}", - orderNo, result.Message); - - return false; - } - } - catch (Exception ex) - { - _logger.LogError(ex, "奖励发放异常: OrderNo={OrderNo}", orderNo); - - order.RewardStatus = 2; // 发放失败 - order.RewardData = ex.Message; - order.UpdatedAt = DateTime.Now; - - await _dbContext.SaveChangesAsync(); - - return false; - } - } - - /// - public async Task> GetUserOrdersAsync(int userId, PaymentOrderQueryRequest request) - { - if (userId <= 0) - throw new ArgumentException("用户ID无效", nameof(userId)); - - request ??= new PaymentOrderQueryRequest(); - - // 确保分页参数有效 - if (request.Page < 1) request.Page = 1; - if (request.PageSize < 1) request.PageSize = 10; - if (request.PageSize > 100) request.PageSize = 100; - - var query = _dbContext.PaymentOrders - .Where(o => o.UserId == userId); - - // 按订单类型筛选 - if (!string.IsNullOrWhiteSpace(request.OrderType)) - { - query = query.Where(o => o.OrderType == request.OrderType); - } - - // 按状态筛选 - if (request.Status.HasValue) - { - query = query.Where(o => o.Status == request.Status.Value); - } - - // 按时间范围筛选 - if (request.StartTime.HasValue) - { - query = query.Where(o => o.CreatedAt >= request.StartTime.Value); - } - - if (request.EndTime.HasValue) - { - query = query.Where(o => o.CreatedAt <= request.EndTime.Value); - } - - // 获取总数 - var total = await query.CountAsync(); - - // 分页查询 - var orders = await query - .OrderByDescending(o => o.CreatedAt) - .Skip((request.Page - 1) * request.PageSize) - .Take(request.PageSize) - .Select(o => new PaymentOrderDto - { - Id = o.Id, - OrderNo = o.OrderNo, - UserId = o.UserId, - OrderType = o.OrderType, - Title = o.Title, - Amount = o.Amount, - PayAmount = o.PayAmount, - PayMethod = o.PayMethod, - Status = o.Status, - PaidAt = o.PaidAt, - TransactionId = o.TransactionId, - BizId = o.BizId, - BizData = o.BizData, - RewardStatus = o.RewardStatus, - RewardData = o.RewardData, - RewardAt = o.RewardAt, - CreatedAt = o.CreatedAt, - UpdatedAt = o.UpdatedAt - }) - .ToListAsync(); - - var lastPage = (int)Math.Ceiling((double)total / request.PageSize); - - return new PageResponse - { - Data = orders, - Total = total, - Page = request.Page, - PageSize = request.PageSize, - LastPage = lastPage - }; - } - - /// - public async Task CancelOrderAsync(string orderNo, int userId) - { - if (string.IsNullOrWhiteSpace(orderNo)) - { - _logger.LogWarning("取消订单失败: 订单号为空"); - return false; - } - - var order = await GetOrderByNoAsync(orderNo); - if (order == null) - { - _logger.LogWarning("取消订单失败: 订单不存在, OrderNo={OrderNo}", orderNo); - return false; - } - - // 验证用户权限 - if (order.UserId != userId) - { - _logger.LogWarning("取消订单失败: 用户无权限, OrderNo={OrderNo}, UserId={UserId}, OrderUserId={OrderUserId}", - orderNo, userId, order.UserId); - return false; - } - - // 只有待支付状态的订单才能取消 - if (order.Status != 0) - { - _logger.LogWarning("取消订单失败: 订单状态不正确, OrderNo={OrderNo}, Status={Status}", orderNo, order.Status); - return false; - } - - order.Status = 2; // 已取消 - order.UpdatedAt = DateTime.Now; - - await _dbContext.SaveChangesAsync(); - - _logger.LogInformation("订单取消成功: OrderNo={OrderNo}, UserId={UserId}", orderNo, userId); - - return true; - } - - /// - public async Task UpdateOrderStatusAsync(string orderNo, byte status) - { - if (string.IsNullOrWhiteSpace(orderNo)) - { - _logger.LogWarning("更新订单状态失败: 订单号为空"); - return false; - } - - var order = await GetOrderByNoAsync(orderNo); - if (order == null) - { - _logger.LogWarning("更新订单状态失败: 订单不存在, OrderNo={OrderNo}", orderNo); - return false; - } - - order.Status = status; - order.UpdatedAt = DateTime.Now; - - await _dbContext.SaveChangesAsync(); - - _logger.LogInformation("订单状态更新成功: OrderNo={OrderNo}, Status={Status}", orderNo, status); - - return true; - } - - /// - /// 生成唯一订单号 - /// 格式: yyyyMMddHHmmss + 6位随机数 - /// - private static string GenerateOrderNo() - { - var timestamp = DateTime.Now.ToString("yyyyMMddHHmmss"); - var random = Random.Shared.Next(100000, 999999); - return $"{timestamp}{random}"; - } -} diff --git a/server/MiAssessment/src/MiAssessment.Core/Services/PaymentRewardDispatcher.cs b/server/MiAssessment/src/MiAssessment.Core/Services/PaymentRewardDispatcher.cs deleted file mode 100644 index 3690615..0000000 --- a/server/MiAssessment/src/MiAssessment.Core/Services/PaymentRewardDispatcher.cs +++ /dev/null @@ -1,136 +0,0 @@ -using MiAssessment.Core.Interfaces; -using MiAssessment.Model.Entities; -using Microsoft.Extensions.Logging; - -namespace MiAssessment.Core.Services; - -/// -/// 支付奖励分发器 -/// 负责根据订单类型查找并调用对应的奖励处理器 -/// -public class PaymentRewardDispatcher : IPaymentRewardDispatcher -{ - private readonly IEnumerable _handlers; - private readonly ILogger _logger; - private readonly Dictionary _handlerMap; - - public PaymentRewardDispatcher( - IEnumerable handlers, - ILogger logger) - { - _handlers = handlers ?? throw new ArgumentNullException(nameof(handlers)); - _logger = logger ?? throw new ArgumentNullException(nameof(logger)); - - // 构建处理器映射表,提高查找效率 - _handlerMap = new Dictionary(StringComparer.OrdinalIgnoreCase); - foreach (var handler in _handlers) - { - if (!string.IsNullOrWhiteSpace(handler.OrderType)) - { - if (_handlerMap.ContainsKey(handler.OrderType)) - { - _logger.LogWarning("发现重复的奖励处理器: OrderType={OrderType}, 已存在={ExistingHandler}, 新处理器={NewHandler}", - handler.OrderType, - _handlerMap[handler.OrderType].GetType().Name, - handler.GetType().Name); - } - else - { - _handlerMap[handler.OrderType] = handler; - _logger.LogDebug("注册奖励处理器: OrderType={OrderType}, Handler={Handler}", - handler.OrderType, handler.GetType().Name); - } - } - } - - _logger.LogInformation("奖励分发器初始化完成,已注册 {Count} 个处理器", _handlerMap.Count); - } - - /// - public IPaymentRewardHandler? GetHandler(string orderType) - { - if (string.IsNullOrWhiteSpace(orderType)) - { - _logger.LogWarning("获取处理器失败: 订单类型为空"); - return null; - } - - if (_handlerMap.TryGetValue(orderType, out var handler)) - { - _logger.LogDebug("找到奖励处理器: OrderType={OrderType}, Handler={Handler}", - orderType, handler.GetType().Name); - return handler; - } - - _logger.LogInformation("未找到订单类型对应的奖励处理器: OrderType={OrderType}", orderType); - return null; - } - - /// - public bool HasHandler(string orderType) - { - if (string.IsNullOrWhiteSpace(orderType)) - return false; - - return _handlerMap.ContainsKey(orderType); - } - - /// - public IReadOnlyCollection GetRegisteredOrderTypes() - { - return _handlerMap.Keys.ToList().AsReadOnly(); - } - - /// - public async Task ProcessRewardAsync(PaymentOrder order) - { - if (order == null) - { - _logger.LogWarning("处理奖励失败: 订单为空"); - return RewardResult.Fail("订单不能为空"); - } - - if (string.IsNullOrWhiteSpace(order.OrderType)) - { - _logger.LogWarning("处理奖励失败: 订单类型为空, OrderNo={OrderNo}", order.OrderNo); - return RewardResult.Fail("订单类型不能为空"); - } - - var handler = GetHandler(order.OrderType); - if (handler == null) - { - // 没有处理器不算失败,可能该订单类型不需要奖励 - _logger.LogInformation("订单类型无需奖励处理: OrderNo={OrderNo}, OrderType={OrderType}", - order.OrderNo, order.OrderType); - return RewardResult.Ok(); - } - - try - { - _logger.LogInformation("开始处理奖励: OrderNo={OrderNo}, OrderType={OrderType}, Handler={Handler}", - order.OrderNo, order.OrderType, handler.GetType().Name); - - var result = await handler.ProcessRewardAsync(order); - - if (result.Success) - { - _logger.LogInformation("奖励处理成功: OrderNo={OrderNo}, OrderType={OrderType}, RewardData={RewardData}", - order.OrderNo, order.OrderType, result.RewardData); - } - else - { - _logger.LogWarning("奖励处理失败: OrderNo={OrderNo}, OrderType={OrderType}, Message={Message}", - order.OrderNo, order.OrderType, result.Message); - } - - return result; - } - catch (Exception ex) - { - _logger.LogError(ex, "奖励处理异常: OrderNo={OrderNo}, OrderType={OrderType}, Handler={Handler}", - order.OrderNo, order.OrderType, handler.GetType().Name); - - return RewardResult.Fail($"奖励处理异常: {ex.Message}"); - } - } -} diff --git a/server/MiAssessment/src/MiAssessment.Core/Services/PaymentService.cs b/server/MiAssessment/src/MiAssessment.Core/Services/PaymentService.cs deleted file mode 100644 index f3ad244..0000000 --- a/server/MiAssessment/src/MiAssessment.Core/Services/PaymentService.cs +++ /dev/null @@ -1,59 +0,0 @@ -using MiAssessment.Core.Interfaces; -using MiAssessment.Model.Data; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Logging; - -namespace MiAssessment.Core.Services; - -/// -/// 支付服务实现 -/// 提供基础的支付相关功能,具体业务逻辑由业务模块扩展 -/// 注意:余额相关字段已移至 UserDetail 扩展表,此处返回默认值 -/// -public class PaymentService : IPaymentService -{ - private readonly MiAssessmentDbContext _dbContext; - private readonly ILogger _logger; - - public PaymentService( - MiAssessmentDbContext dbContext, - ILogger logger) - { - _dbContext = dbContext; - _logger = logger; - } - - /// - /// - /// 余额字段已从 User 实体移除,此方法需要在业务层重新实现 - /// 当前返回 false,表示不支持余额支付 - /// - public async Task ValidateBalanceAsync(int userId, decimal amount) - { - var user = await _dbContext.Users.FindAsync(userId); - if (user == null) - return false; - - // 余额字段已移至 UserDetail 扩展表 - // 业务层需要实现具体的余额验证逻辑 - _logger.LogWarning("ValidateBalanceAsync: 余额字段已移至 UserDetail 扩展表,请在业务层实现具体逻辑"); - return false; - } - - /// - /// - /// 余额字段已从 User 实体移除,此方法需要在业务层重新实现 - /// 当前返回 0 - /// - public async Task GetUserBalanceAsync(int userId) - { - var user = await _dbContext.Users.FindAsync(userId); - if (user == null) - return 0; - - // 余额字段已移至 UserDetail 扩展表 - // 业务层需要实现具体的余额查询逻辑 - _logger.LogWarning("GetUserBalanceAsync: 余额字段已移至 UserDetail 扩展表,请在业务层实现具体逻辑"); - return 0; - } -} diff --git a/server/MiAssessment/src/MiAssessment.Core/Services/UserService.cs b/server/MiAssessment/src/MiAssessment.Core/Services/UserService.cs index 43a1e32..b225775 100644 --- a/server/MiAssessment/src/MiAssessment.Core/Services/UserService.cs +++ b/server/MiAssessment/src/MiAssessment.Core/Services/UserService.cs @@ -50,7 +50,7 @@ public class UserService : BaseService, IUserService if (string.IsNullOrWhiteSpace(mobile)) return null; - return await _dbSet.FirstOrDefaultAsync(u => u.Mobile == mobile); + return await _dbSet.FirstOrDefaultAsync(u => u.Phone == mobile); } /// @@ -66,15 +66,15 @@ public class UserService : BaseService, IUserService { OpenId = dto.OpenId ?? string.Empty, UnionId = dto.UnionId, - Mobile = dto.Mobile, + Phone = dto.Mobile, Uid = uid, Nickname = dto.Nickname ?? $"User{Random.Shared.Next(1000, 9999)}", - HeadImg = dto.Headimg ?? string.Empty, - Pid = dto.Pid, + Avatar = dto.Headimg ?? string.Empty, + ParentUserId = dto.Pid, Status = 1, IsTest = 0, - CreatedAt = DateTime.UtcNow, - UpdatedAt = DateTime.UtcNow + CreateTime = DateTime.Now, + UpdateTime = DateTime.Now }; await _dbSet.AddAsync(user); @@ -99,15 +99,15 @@ public class UserService : BaseService, IUserService user.Nickname = dto.Nickname; if (!string.IsNullOrWhiteSpace(dto.Headimg)) - user.HeadImg = dto.Headimg; + user.Avatar = dto.Headimg; if (!string.IsNullOrWhiteSpace(dto.Mobile)) - user.Mobile = dto.Mobile; + user.Phone = dto.Mobile; if (!string.IsNullOrWhiteSpace(dto.UnionId)) user.UnionId = dto.UnionId; - user.UpdatedAt = DateTime.UtcNow; + user.UpdateTime = DateTime.Now; _dbSet.Update(user); await _dbContext.SaveChangesAsync(); @@ -122,17 +122,17 @@ public class UserService : BaseService, IUserService if (user == null) return null; - var registrationDays = (int)(DateTime.UtcNow - user.CreatedAt).TotalDays; + var registrationDays = (int)(DateTime.Now - user.CreateTime).TotalDays; - var maskedMobile = MaskMobileNumber(user.Mobile); - var mobileIs = string.IsNullOrWhiteSpace(user.Mobile) ? 0 : 1; + var maskedMobile = MaskMobileNumber(user.Phone); + var mobileIs = string.IsNullOrWhiteSpace(user.Phone) ? 0 : 1; return new UserInfoDto { Id = user.Id, Uid = user.Uid, Nickname = user.Nickname, - Headimg = user.HeadImg, + Headimg = user.Avatar, Mobile = maskedMobile, MobileIs = mobileIs, Money = 0, // 业务字段已移除,返回默认值 diff --git a/server/MiAssessment/src/MiAssessment.Core/Services/WechatPayService.cs b/server/MiAssessment/src/MiAssessment.Core/Services/WechatPayService.cs index abd4241..1df1f44 100644 --- a/server/MiAssessment/src/MiAssessment.Core/Services/WechatPayService.cs +++ b/server/MiAssessment/src/MiAssessment.Core/Services/WechatPayService.cs @@ -299,8 +299,8 @@ public class WechatPayService : IWechatPayService RetryCount = 0, Attach = attach, OpenId = openId, - CreatedAt = DateTime.Now, - UpdatedAt = DateTime.Now + CreateTime = DateTime.Now, + UpdateTime = DateTime.Now }; _dbContext.OrderNotifies.Add(orderNotify); diff --git a/server/MiAssessment/src/MiAssessment.Core/Services/WechatPayV3Service.cs b/server/MiAssessment/src/MiAssessment.Core/Services/WechatPayV3Service.cs index 196ccc3..708cea7 100644 --- a/server/MiAssessment/src/MiAssessment.Core/Services/WechatPayV3Service.cs +++ b/server/MiAssessment/src/MiAssessment.Core/Services/WechatPayV3Service.cs @@ -882,8 +882,8 @@ public class WechatPayV3Service : IWechatPayV3Service RetryCount = 0, Attach = attach, OpenId = openId, - CreatedAt = DateTime.Now, - UpdatedAt = DateTime.Now + CreateTime = DateTime.Now, + UpdateTime = DateTime.Now }; _dbContext.OrderNotifies.Add(orderNotify); diff --git a/server/MiAssessment/src/MiAssessment.Core/Services/WechatService.cs b/server/MiAssessment/src/MiAssessment.Core/Services/WechatService.cs index 7cee6e6..ba3adba 100644 --- a/server/MiAssessment/src/MiAssessment.Core/Services/WechatService.cs +++ b/server/MiAssessment/src/MiAssessment.Core/Services/WechatService.cs @@ -540,9 +540,8 @@ public class WechatService : IWechatService RetryCount = 0, Attach = request.Attach, OpenId = request.OpenId, - Extend = JsonSerializer.Serialize(new { orderType = request.Attach, title = title }), - CreatedAt = DateTime.UtcNow, - UpdatedAt = DateTime.UtcNow + CreateTime = DateTime.Now, + UpdateTime = DateTime.Now }; _dbContext.OrderNotifies.Add(orderNotify); diff --git a/server/MiAssessment/src/MiAssessment.Infrastructure/Modules/ServiceModule.cs b/server/MiAssessment/src/MiAssessment.Infrastructure/Modules/ServiceModule.cs index b4a9653..9f91d87 100644 --- a/server/MiAssessment/src/MiAssessment.Infrastructure/Modules/ServiceModule.cs +++ b/server/MiAssessment/src/MiAssessment.Infrastructure/Modules/ServiceModule.cs @@ -58,16 +58,6 @@ public class ServiceModule : Module return new AuthService(dbContext, userService, jwtService, wechatService, ipLocationService, redisService, jwtSettings, logger); }).As().InstancePerLifetimeScope(); - // ========== 用户管理系统服务注册 ========== - - // 注册地址服务 - builder.Register(c => - { - var dbContext = c.Resolve(); - var logger = c.Resolve>(); - return new AddressService(dbContext, logger); - }).As().InstancePerLifetimeScope(); - // ========== 支付系统服务注册 ========== // 注册微信支付配置服务(从Admin库读取配置) @@ -105,15 +95,7 @@ public class ServiceModule : Module return new WechatPayService(dbContext, httpClientFactory.CreateClient(), logger, configService, wechatService, redisService, settings, appSettings, v3ServiceLazy); }).As().InstancePerLifetimeScope(); - // 注册支付服务 - builder.Register(c => - { - var dbContext = c.Resolve(); - var logger = c.Resolve>(); - return new PaymentService(dbContext, logger); - }).As().InstancePerLifetimeScope(); - - // 注册支付回调服务 + // ========== 配置系统服务注册 ========== builder.Register(c => { var dbContext = c.Resolve(); @@ -124,33 +106,6 @@ public class ServiceModule : Module return new PaymentNotifyService(dbContext, wechatPayService, wechatPayV3Service, wechatPayConfigService, logger); }).As().InstancePerLifetimeScope(); - // 注册支付订单服务 - builder.Register(c => - { - var dbContext = c.Resolve(); - var rewardHandlers = c.Resolve>(); - var logger = c.Resolve>(); - return new PaymentOrderService(dbContext, rewardHandlers, logger); - }).As().InstancePerLifetimeScope(); - - // 注册奖励分发器 - builder.Register(c => - { - var rewardHandlers = c.Resolve>(); - var logger = c.Resolve>(); - return new PaymentRewardDispatcher(rewardHandlers, logger); - }).As().SingleInstance(); - - // ========== 奖励处理器注册 ========== - // 注册默认奖励处理器(示例) - // 实际项目中可以注册多个处理器,每个处理器处理不同的订单类型 - // 例如:DiamondRechargeRewardHandler, VipPurchaseRewardHandler 等 - builder.Register(c => - { - var logger = c.Resolve>(); - return new DefaultPaymentRewardHandler(logger); - }).As().InstancePerLifetimeScope(); - // ========== 配置系统服务注册 ========== // 注册配置服务 diff --git a/server/MiAssessment/src/MiAssessment.Model/Data/MiAssessmentDbContext.cs b/server/MiAssessment/src/MiAssessment.Model/Data/MiAssessmentDbContext.cs index 59fd272..1fd0ae0 100644 --- a/server/MiAssessment/src/MiAssessment.Model/Data/MiAssessmentDbContext.cs +++ b/server/MiAssessment/src/MiAssessment.Model/Data/MiAssessmentDbContext.cs @@ -16,20 +16,9 @@ public partial class MiAssessmentDbContext : DbContext { } - // ==================== Admin 基础表 ==================== - public virtual DbSet Admins { get; set; } - - public virtual DbSet AdminLoginLogs { get; set; } - - public virtual DbSet AdminOperationLogs { get; set; } - // ==================== 用户基础表 ==================== public virtual DbSet Users { get; set; } - public virtual DbSet UserDetails { get; set; } - - public virtual DbSet UserAddresses { get; set; } - public virtual DbSet UserRefreshTokens { get; set; } public virtual DbSet UserLoginLogs { get; set; } @@ -39,12 +28,6 @@ public partial class MiAssessmentDbContext : DbContext public virtual DbSet OrderNotifies { get; set; } - public virtual DbSet PaymentOrders { get; set; } - - public virtual DbSet Pictures { get; set; } - - public virtual DbSet Deliveries { get; set; } - // ==================== 小程序业务表 ==================== public virtual DbSet Banners { get; set; } @@ -86,131 +69,6 @@ public partial class MiAssessmentDbContext : DbContext { modelBuilder.UseCollation("Chinese_PRC_CI_AS"); - // ==================== Admin 基础表配置 ==================== - modelBuilder.Entity(entity => - { - entity.HasKey(e => e.Id).HasName("pk_admins"); - - entity.ToTable("admins", tb => tb.HasComment("管理员表,存储后台管理员信息")); - - entity.HasIndex(e => e.Status, "ix_admins_status"); - - entity.HasIndex(e => e.Token, "ix_admins_token"); - - entity.HasIndex(e => e.Username, "ix_admins_username"); - - entity.Property(e => e.Id) - .HasComment("主键ID") - .HasColumnName("id"); - entity.Property(e => e.AdminId) - .HasComment("上级管理员ID") - .HasColumnName("admin_id"); - entity.Property(e => e.CreatedAt) - .HasDefaultValueSql("(getdate())") - .HasComment("创建时间") - .HasColumnName("created_at"); - entity.Property(e => e.GetTime) - .HasDefaultValueSql("(getdate())") - .HasComment("获取时间") - .HasColumnName("get_time"); - entity.Property(e => e.Nickname) - .HasMaxLength(20) - .HasComment("昵称") - .HasColumnName("nickname"); - entity.Property(e => e.Password) - .HasMaxLength(40) - .HasComment("密码(加密)") - .HasColumnName("password"); - entity.Property(e => e.Qid) - .HasComment("权限组ID") - .HasColumnName("qid"); - entity.Property(e => e.Random) - .HasMaxLength(20) - .HasComment("随机字符串") - .HasColumnName("random"); - entity.Property(e => e.Status) - .HasDefaultValue(0) - .HasComment("状态:0-正常") - .HasColumnName("status"); - entity.Property(e => e.Token) - .HasMaxLength(100) - .HasComment("登录令牌") - .HasColumnName("token"); - entity.Property(e => e.UpdatedAt) - .HasDefaultValueSql("(getdate())") - .HasComment("更新时间") - .HasColumnName("updated_at"); - entity.Property(e => e.Username) - .HasMaxLength(20) - .HasComment("用户名") - .HasColumnName("username"); - }); - - modelBuilder.Entity(entity => - { - entity.HasKey(e => e.Id).HasName("pk_admin_login_logs"); - - entity.ToTable("admin_login_logs", tb => tb.HasComment("管理员登录日志表,记录管理员登录信息(仅结构,不迁移历史数据)")); - - entity.HasIndex(e => e.AdminId, "ix_admin_login_logs_admin_id"); - - entity.HasIndex(e => e.CreatedAt, "ix_admin_login_logs_created_at"); - - entity.HasIndex(e => e.Ip, "ix_admin_login_logs_ip"); - - entity.Property(e => e.Id) - .HasComment("主键ID") - .HasColumnName("id"); - entity.Property(e => e.AdminId) - .HasComment("管理员ID") - .HasColumnName("admin_id"); - entity.Property(e => e.CreatedAt) - .HasDefaultValueSql("(getdate())") - .HasComment("登录时间") - .HasColumnName("created_at"); - entity.Property(e => e.Ip) - .HasMaxLength(50) - .HasComment("登录IP地址") - .HasColumnName("ip"); - }); - - modelBuilder.Entity(entity => - { - entity.HasKey(e => e.Id).HasName("pk_admin_operation_logs"); - - entity.ToTable("admin_operation_logs", tb => tb.HasComment("管理员操作日志表,记录管理员操作信息(仅结构,不迁移历史数据)")); - - entity.HasIndex(e => e.AdminId, "ix_admin_operation_logs_admin_id"); - - entity.HasIndex(e => e.CreatedAt, "ix_admin_operation_logs_created_at"); - - entity.HasIndex(e => e.Ip, "ix_admin_operation_logs_ip"); - - entity.HasIndex(e => e.Operation, "ix_admin_operation_logs_operation"); - - entity.Property(e => e.Id) - .HasComment("主键ID") - .HasColumnName("id"); - entity.Property(e => e.AdminId) - .HasComment("管理员ID") - .HasColumnName("admin_id"); - entity.Property(e => e.Content) - .HasComment("操作内容详情") - .HasColumnName("content"); - entity.Property(e => e.CreatedAt) - .HasDefaultValueSql("(getdate())") - .HasComment("操作时间") - .HasColumnName("created_at"); - entity.Property(e => e.Ip) - .HasMaxLength(50) - .HasComment("操作IP地址") - .HasColumnName("ip"); - entity.Property(e => e.Operation) - .HasMaxLength(255) - .HasComment("操作名称") - .HasColumnName("operation"); - }); - // ==================== 用户基础表配置 ==================== modelBuilder.Entity(entity => { @@ -218,178 +76,75 @@ public partial class MiAssessmentDbContext : DbContext entity.ToTable("users", tb => tb.HasComment("用户主表,存储用户基本信息")); - entity.HasIndex(e => e.CreatedAt, "ix_users_created_at"); - - entity.HasIndex(e => e.Mobile, "ix_users_mobile").HasFilter("([mobile] IS NOT NULL)"); - + entity.HasIndex(e => e.CreateTime, "ix_users_created_at"); + entity.HasIndex(e => e.Phone, "ix_users_mobile").HasFilter("([Phone] IS NOT NULL)"); entity.HasIndex(e => e.OpenId, "ix_users_open_id"); - - entity.HasIndex(e => e.Pid, "ix_users_pid"); - + entity.HasIndex(e => e.ParentUserId, "ix_users_pid"); entity.HasIndex(e => e.Status, "ix_users_status"); - entity.HasIndex(e => e.Uid, "uk_users_uid").IsUnique(); entity.Property(e => e.Id) - .HasComment("主键ID") - .HasColumnName("id"); - entity.Property(e => e.CreatedAt) - .HasDefaultValueSql("(getdate())") - .HasComment("创建时间") - .HasColumnName("created_at"); - entity.Property(e => e.GzhOpenId) - .HasMaxLength(255) - .HasComment("公众号openid") - .HasColumnName("gzh_open_id"); - entity.Property(e => e.HeadImg) - .HasMaxLength(255) - .HasComment("头像URL") - .HasColumnName("head_img"); - entity.Property(e => e.IsTest) - .HasComment("是否测试账号: 0否 1是") - .HasColumnName("is_test"); - entity.Property(e => e.LastLoginTime) - .HasComment("最后登录时间") - .HasColumnName("last_login_time"); - entity.Property(e => e.LastLoginIp) - .HasMaxLength(50) - .HasComment("最后登录IP") - .HasColumnName("last_login_ip"); - entity.Property(e => e.Mobile) - .HasMaxLength(15) - .HasComment("手机号") - .HasColumnName("mobile"); - entity.Property(e => e.Nickname) - .HasMaxLength(255) - .HasComment("昵称") - .HasColumnName("nickname"); + .HasComment("主键ID"); + entity.Property(e => e.Uid) + .HasMaxLength(6) + .HasComment("用户唯一标识"); entity.Property(e => e.OpenId) + .HasMaxLength(64) + .HasComment("微信openid"); + entity.Property(e => e.UnionId) + .HasMaxLength(64) + .HasComment("微信unionid"); + entity.Property(e => e.GzhOpenId) + .HasMaxLength(64) + .HasComment("公众号openid"); + entity.Property(e => e.Phone) + .HasMaxLength(20) + .HasComment("手机号"); + entity.Property(e => e.Nickname) .HasMaxLength(50) - .HasComment("微信openid") - .HasColumnName("open_id"); - entity.Property(e => e.Password) - .HasMaxLength(40) - .HasComment("密码") - .HasColumnName("password"); - entity.Property(e => e.Pid) - .HasComment("推荐人ID") - .HasColumnName("pid"); + .HasComment("昵称"); + entity.Property(e => e.Avatar) + .HasMaxLength(500) + .HasComment("头像URL"); entity.Property(e => e.UserLevel) .HasDefaultValue(1) - .HasComment("用户等级:1普通用户 2合伙人 3渠道合伙人") - .HasColumnName("user_level"); + .HasComment("用户等级:1普通用户 2合伙人 3渠道合伙人"); + entity.Property(e => e.ParentUserId) + .HasComment("推荐人用户ID"); entity.Property(e => e.InviteCode) .HasMaxLength(10) - .HasComment("用户专属邀请码") - .HasColumnName("invite_code"); + .HasComment("用户专属邀请码"); entity.Property(e => e.Balance) .HasDefaultValue(0m) .HasComment("可提现余额") - .HasColumnType("decimal(10, 2)") - .HasColumnName("balance"); + .HasColumnType("decimal(10, 2)"); entity.Property(e => e.TotalIncome) .HasDefaultValue(0m) .HasComment("累计收益") - .HasColumnType("decimal(10, 2)") - .HasColumnName("total_income"); + .HasColumnType("decimal(10, 2)"); entity.Property(e => e.WithdrawnAmount) .HasDefaultValue(0m) .HasComment("已提现金额") - .HasColumnType("decimal(10, 2)") - .HasColumnName("withdrawn_amount"); + .HasColumnType("decimal(10, 2)"); entity.Property(e => e.Status) - .HasDefaultValue((byte)1) - .HasComment("状态: 1正常 0禁用") - .HasColumnName("status"); - entity.Property(e => e.Uid) - .HasMaxLength(16) - .HasComment("用户唯一标识") - .HasColumnName("uid"); - entity.Property(e => e.UnionId) - .HasMaxLength(255) - .HasComment("微信unionid") - .HasColumnName("union_id"); - entity.Property(e => e.UpdatedAt) - .HasDefaultValueSql("(getdate())") - .HasComment("更新时间") - .HasColumnName("updated_at"); - }); - - modelBuilder.Entity(entity => - { - entity.HasKey(e => e.Id).HasName("pk_user_details"); - - entity.ToTable("user_details", tb => tb.HasComment("用户详情扩展表,用于存储业务扩展字段(余额、积分、等级等)")); - - entity.HasIndex(e => e.UserId, "uk_user_details_user_id").IsUnique(); - - entity.Property(e => e.Id) - .HasComment("主键ID") - .HasColumnName("id"); - entity.Property(e => e.UserId) - .HasComment("用户ID(唯一)") - .HasColumnName("user_id"); - entity.Property(e => e.CreatedAt) - .HasDefaultValueSql("(getdate())") - .HasComment("创建时间") - .HasColumnName("created_at"); - entity.Property(e => e.UpdatedAt) - .HasDefaultValueSql("(getdate())") - .HasComment("更新时间") - .HasColumnName("updated_at"); - - // 配置与 User 的一对一关系 - entity.HasOne(e => e.User) - .WithOne(u => u.UserDetail) - .HasForeignKey(e => e.UserId) - .OnDelete(DeleteBehavior.Cascade) - .HasConstraintName("fk_user_details_users"); - }); - - modelBuilder.Entity(entity => - { - entity.HasKey(e => e.Id).HasName("pk_user_addresses"); - - entity.ToTable("user_addresses", tb => tb.HasComment("用户收货地址表,存储用户的收货地址信息")); - - entity.HasIndex(e => new { e.UserId, e.IsDefault }, "ix_user_addresses_is_default").HasFilter("([is_deleted]=(0))"); - - entity.HasIndex(e => e.UserId, "ix_user_addresses_user_id"); - - entity.Property(e => e.Id) - .HasComment("主键ID") - .HasColumnName("id"); - entity.Property(e => e.CreatedAt) - .HasDefaultValueSql("(getdate())") - .HasComment("创建时间") - .HasColumnName("created_at"); - entity.Property(e => e.DetailedAddress) - .HasMaxLength(255) - .HasComment("详细地址") - .HasColumnName("detailed_address"); - entity.Property(e => e.IsDefault) - .HasDefaultValue((byte)0) - .HasComment("是否默认地址: 0否 1是") - .HasColumnName("is_default"); - entity.Property(e => e.IsDeleted) - .HasDefaultValue((byte)0) - .HasComment("是否删除: 0否 1是") - .HasColumnName("is_deleted"); - entity.Property(e => e.ReceiverName) + .HasDefaultValue(1) + .HasComment("状态: 1正常 0禁用"); + entity.Property(e => e.IsTest) + .HasComment("是否测试账号: 0否 1是"); + entity.Property(e => e.LastLoginTime) + .HasComment("最后登录时间"); + entity.Property(e => e.LastLoginIp) .HasMaxLength(50) - .HasComment("收货人姓名") - .HasColumnName("receiver_name"); - entity.Property(e => e.ReceiverPhone) - .HasMaxLength(20) - .HasComment("收货人电话") - .HasColumnName("receiver_phone"); - entity.Property(e => e.UpdatedAt) + .HasComment("最后登录IP"); + entity.Property(e => e.CreateTime) .HasDefaultValueSql("(getdate())") - .HasComment("更新时间") - .HasColumnName("updated_at"); - entity.Property(e => e.UserId) - .HasComment("用户ID") - .HasColumnName("user_id"); + .HasComment("创建时间"); + entity.Property(e => e.UpdateTime) + .HasDefaultValueSql("(getdate())") + .HasComment("更新时间"); + entity.Property(e => e.IsDeleted) + .HasDefaultValue(false) + .HasComment("软删除标记"); }); modelBuilder.Entity(entity => @@ -403,37 +158,28 @@ public partial class MiAssessmentDbContext : DbContext entity.HasIndex(e => e.ExpiresAt, "ix_user_refresh_tokens_expires_at"); entity.Property(e => e.Id) - .HasComment("主键ID") - .HasColumnName("id"); + .HasComment("主键ID"); entity.Property(e => e.UserId) - .HasComment("用户ID") - .HasColumnName("user_id"); + .HasComment("用户ID"); entity.Property(e => e.TokenHash) .HasMaxLength(256) - .HasComment("Token 哈希值(SHA256)") - .HasColumnName("token_hash"); + .HasComment("Token 哈希值(SHA256)"); entity.Property(e => e.ExpiresAt) - .HasComment("过期时间") - .HasColumnName("expires_at"); + .HasComment("过期时间"); entity.Property(e => e.CreatedAt) .HasDefaultValueSql("(getdate())") - .HasComment("创建时间") - .HasColumnName("created_at"); + .HasComment("创建时间"); entity.Property(e => e.CreatedByIp) .HasMaxLength(50) - .HasComment("创建时的 IP 地址") - .HasColumnName("created_by_ip"); + .HasComment("创建时的 IP 地址"); entity.Property(e => e.RevokedAt) - .HasComment("撤销时间") - .HasColumnName("revoked_at"); + .HasComment("撤销时间"); entity.Property(e => e.RevokedByIp) .HasMaxLength(50) - .HasComment("撤销时的 IP 地址") - .HasColumnName("revoked_by_ip"); + .HasComment("撤销时的 IP 地址"); entity.Property(e => e.ReplacedByToken) .HasMaxLength(256) - .HasComment("被替换的新 Token 哈希值") - .HasColumnName("replaced_by_token"); + .HasComment("被替换的新 Token 哈希值"); entity.HasOne(e => e.User) .WithMany() @@ -446,54 +192,35 @@ public partial class MiAssessmentDbContext : DbContext { entity.HasKey(e => e.Id).HasName("pk_user_login_logs"); - entity.ToTable("user_login_logs", tb => tb.HasComment("用户登录日志表,记录用户每次登录的时间、设备和位置信息")); - - entity.HasIndex(e => e.LoginDate, "ix_user_login_logs_login_date"); - - entity.HasIndex(e => e.LoginTime, "ix_user_login_logs_login_time"); + entity.ToTable("user_login_logs", tb => tb.HasComment("用户登录日志表,记录用户每次登录信息")); entity.HasIndex(e => e.UserId, "ix_user_login_logs_user_id"); - entity.HasIndex(e => e.Year, "ix_user_login_logs_year"); - - entity.HasIndex(e => new { e.Year, e.Month }, "ix_user_login_logs_year_month"); - entity.Property(e => e.Id) - .HasComment("主键ID") - .HasColumnName("id"); - entity.Property(e => e.Device) - .HasMaxLength(50) - .HasComment("设备类型") - .HasColumnName("device"); - entity.Property(e => e.Ip) - .HasMaxLength(50) - .HasComment("登录IP") - .HasColumnName("ip"); - entity.Property(e => e.LastLoginTime) - .HasComment("最后登录时间") - .HasColumnName("last_login_time"); - entity.Property(e => e.Location) - .HasMaxLength(100) - .HasComment("登录位置") - .HasColumnName("location"); - entity.Property(e => e.LoginDate) - .HasComment("登录日期") - .HasColumnName("login_date"); - entity.Property(e => e.LoginTime) - .HasComment("登录时间") - .HasColumnName("login_time"); - entity.Property(e => e.Month) - .HasComment("月份") - .HasColumnName("month"); + .HasComment("主键ID"); entity.Property(e => e.UserId) - .HasComment("用户ID") - .HasColumnName("user_id"); - entity.Property(e => e.Week) - .HasComment("周数") - .HasColumnName("week"); - entity.Property(e => e.Year) - .HasComment("年份") - .HasColumnName("year"); + .HasComment("用户ID"); + entity.Property(e => e.LoginType) + .HasMaxLength(20) + .HasComment("登录类型"); + entity.Property(e => e.LoginIp) + .HasMaxLength(50) + .HasComment("登录IP"); + entity.Property(e => e.UserAgent) + .HasMaxLength(500) + .HasComment("用户代理"); + entity.Property(e => e.Platform) + .HasMaxLength(20) + .HasComment("平台"); + entity.Property(e => e.Status) + .HasDefaultValue(1) + .HasComment("状态:1成功 0失败"); + entity.Property(e => e.FailReason) + .HasMaxLength(200) + .HasComment("失败原因"); + entity.Property(e => e.CreateTime) + .HasDefaultValueSql("(getdate())") + .HasComment("创建时间"); }); // ==================== 系统基础表配置 ==================== @@ -501,22 +228,36 @@ public partial class MiAssessmentDbContext : DbContext { entity.HasKey(e => e.Id).HasName("pk_configs"); - entity.ToTable("configs", tb => tb.HasComment("系统配置表,存储系统各项配置信息")); + entity.ToTable("configs", tb => tb.HasComment("业务配置表,存储业务相关配置信息")); entity.HasIndex(e => e.ConfigKey, "ix_configs_key"); entity.HasIndex(e => e.ConfigKey, "uk_configs_key").IsUnique(); entity.Property(e => e.Id) - .HasComment("主键ID") - .HasColumnName("id"); + .HasComment("主键ID"); entity.Property(e => e.ConfigKey) - .HasMaxLength(255) - .HasComment("配置键名") - .HasColumnName("config_key"); + .HasMaxLength(100) + .HasComment("配置键名"); entity.Property(e => e.ConfigValue) - .HasComment("配置值(JSON格式)") - .HasColumnName("config_value"); + .HasComment("配置值(JSON格式)"); + entity.Property(e => e.ConfigType) + .HasMaxLength(50) + .HasComment("配置类型"); + entity.Property(e => e.Description) + .HasMaxLength(500) + .HasComment("描述"); + entity.Property(e => e.Sort) + .HasComment("排序"); + entity.Property(e => e.CreateTime) + .HasDefaultValueSql("(getdate())") + .HasComment("创建时间"); + entity.Property(e => e.UpdateTime) + .HasDefaultValueSql("(getdate())") + .HasComment("更新时间"); + entity.Property(e => e.IsDeleted) + .HasDefaultValue(false) + .HasComment("软删除标记"); }); modelBuilder.Entity(entity => @@ -531,212 +272,50 @@ public partial class MiAssessmentDbContext : DbContext entity.HasIndex(e => e.Status, "ix_order_notifies_status"); - entity.HasIndex(e => e.CreatedAt, "ix_order_notifies_created_at"); + entity.HasIndex(e => e.CreateTime, "ix_order_notifies_created_at"); entity.Property(e => e.Id) - .HasComment("主键ID") - .HasColumnName("id"); + .HasComment("主键ID"); entity.Property(e => e.OrderNo) .HasMaxLength(64) - .HasComment("商户订单号") - .HasColumnName("order_no"); + .HasComment("商户订单号"); entity.Property(e => e.TransactionId) .HasMaxLength(64) - .HasComment("微信支付订单号") - .HasColumnName("transaction_id"); + .HasComment("微信支付订单号"); entity.Property(e => e.NotifyUrl) - .HasMaxLength(255) - .HasComment("回调通知URL") - .HasColumnName("notify_url"); + .HasMaxLength(500) + .HasComment("回调通知URL"); entity.Property(e => e.NonceStr) .HasMaxLength(64) - .HasComment("随机字符串") - .HasColumnName("nonce_str"); + .HasComment("随机字符串"); entity.Property(e => e.PayTime) - .HasComment("支付时间") - .HasColumnName("pay_time"); + .HasComment("支付时间"); entity.Property(e => e.PayAmount) .HasComment("支付金额") - .HasColumnType("decimal(10, 2)") - .HasColumnName("pay_amount"); + .HasColumnType("decimal(10, 2)"); entity.Property(e => e.Status) - .HasDefaultValue((byte)0) - .HasComment("处理状态:0=待处理,1=处理成功,2=处理失败") - .HasColumnName("status"); + .HasDefaultValue(0) + .HasComment("处理状态:0=待处理,1=处理成功,2=处理失败"); entity.Property(e => e.RetryCount) .HasDefaultValue(0) - .HasComment("重试次数") - .HasColumnName("retry_count"); + .HasComment("重试次数"); entity.Property(e => e.Attach) - .HasMaxLength(128) - .HasComment("附加数据(订单类型)") - .HasColumnName("attach"); + .HasMaxLength(100) + .HasComment("附加数据(订单类型)"); entity.Property(e => e.OpenId) - .HasMaxLength(64) - .HasComment("用户OpenId") - .HasColumnName("open_id"); + .HasMaxLength(100) + .HasComment("用户OpenId"); entity.Property(e => e.RawData) - .HasComment("原始回调数据") - .HasColumnName("raw_data"); + .HasComment("原始回调数据"); entity.Property(e => e.ErrorMessage) .HasMaxLength(500) - .HasComment("错误信息") - .HasColumnName("error_message"); - entity.Property(e => e.CreatedAt) + .HasComment("错误信息"); + entity.Property(e => e.CreateTime) .HasDefaultValueSql("(getdate())") - .HasComment("创建时间") - .HasColumnName("created_at"); - entity.Property(e => e.UpdatedAt) + .HasComment("创建时间"); + entity.Property(e => e.UpdateTime) .HasDefaultValueSql("(getdate())") - .HasComment("更新时间") - .HasColumnName("updated_at"); - }); - - modelBuilder.Entity(entity => - { - entity.HasKey(e => e.Id).HasName("pk_payment_orders"); - - entity.ToTable("payment_orders", tb => tb.HasComment("通用支付订单表,支持多种订单类型和奖励发放机制")); - - entity.HasIndex(e => e.OrderNo, "uk_payment_orders_order_no").IsUnique(); - - entity.HasIndex(e => e.UserId, "ix_payment_orders_user_id"); - - entity.HasIndex(e => e.OrderType, "ix_payment_orders_order_type"); - - entity.HasIndex(e => e.Status, "ix_payment_orders_status"); - - entity.HasIndex(e => e.CreatedAt, "ix_payment_orders_created_at"); - - entity.Property(e => e.Id) - .HasComment("主键ID") - .HasColumnName("id"); - entity.Property(e => e.OrderNo) - .HasMaxLength(64) - .HasComment("订单号(唯一)") - .HasColumnName("order_no"); - entity.Property(e => e.UserId) - .HasComment("用户ID") - .HasColumnName("user_id"); - entity.Property(e => e.OrderType) - .HasMaxLength(50) - .HasComment("订单类型(如:diamond_recharge, vip_purchase 等)") - .HasColumnName("order_type"); - entity.Property(e => e.Title) - .HasMaxLength(100) - .HasComment("订单标题") - .HasColumnName("title"); - entity.Property(e => e.Amount) - .HasComment("订单金额(单位:元)") - .HasColumnType("decimal(10, 2)") - .HasColumnName("amount"); - entity.Property(e => e.PayAmount) - .HasComment("实付金额(单位:元)") - .HasColumnType("decimal(10, 2)") - .HasColumnName("pay_amount"); - entity.Property(e => e.PayMethod) - .HasMaxLength(20) - .HasComment("支付方式(如:wechat, alipay 等)") - .HasColumnName("pay_method"); - entity.Property(e => e.Status) - .HasDefaultValue((byte)0) - .HasComment("状态:0-待支付 1-已支付 2-已取消 3-已退款") - .HasColumnName("status"); - entity.Property(e => e.PaidAt) - .HasComment("支付时间") - .HasColumnName("paid_at"); - entity.Property(e => e.TransactionId) - .HasMaxLength(64) - .HasComment("第三方交易号") - .HasColumnName("transaction_id"); - entity.Property(e => e.BizId) - .HasComment("业务关联ID") - .HasColumnName("biz_id"); - entity.Property(e => e.BizData) - .HasComment("业务扩展数据(JSON格式)") - .HasColumnName("biz_data"); - entity.Property(e => e.RewardStatus) - .HasDefaultValue((byte)0) - .HasComment("奖励状态:0-未发放 1-已发放 2-发放失败") - .HasColumnName("reward_status"); - entity.Property(e => e.RewardData) - .HasComment("奖励数据(JSON格式)") - .HasColumnName("reward_data"); - entity.Property(e => e.RewardAt) - .HasComment("奖励发放时间") - .HasColumnName("reward_at"); - entity.Property(e => e.CreatedAt) - .HasDefaultValueSql("(getdate())") - .HasComment("创建时间") - .HasColumnName("created_at"); - entity.Property(e => e.UpdatedAt) - .HasDefaultValueSql("(getdate())") - .HasComment("更新时间") - .HasColumnName("updated_at"); - - // 配置与 User 的关系 - entity.HasOne(e => e.User) - .WithMany() - .HasForeignKey(e => e.UserId) - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_payment_orders_users"); - }); - - modelBuilder.Entity(entity => - { - entity.HasKey(e => e.Id).HasName("pk_pictures"); - - entity.ToTable("pictures", tb => tb.HasComment("图片管理表,存储上传的图片信息")); - - entity.HasIndex(e => e.Status, "ix_pictures_status"); - - entity.HasIndex(e => e.Token, "ix_pictures_token"); - - entity.HasIndex(e => e.Type, "ix_pictures_type"); - - entity.Property(e => e.Id) - .HasComment("主键ID") - .HasColumnName("id"); - entity.Property(e => e.CreatedAt) - .HasDefaultValueSql("(getdate())") - .HasComment("创建时间") - .HasColumnName("created_at"); - entity.Property(e => e.ImgUrl) - .HasMaxLength(255) - .HasComment("图片URL地址") - .HasColumnName("img_url"); - entity.Property(e => e.Status) - .HasDefaultValue((byte)1) - .HasComment("状态:1-正常") - .HasColumnName("status"); - entity.Property(e => e.Token) - .HasMaxLength(255) - .HasComment("图片令牌/标识") - .HasColumnName("token"); - entity.Property(e => e.Type) - .HasComment("图片类型") - .HasColumnName("type"); - }); - - modelBuilder.Entity(entity => - { - entity.HasKey(e => e.Id).HasName("pk_deliveries"); - - entity.ToTable("deliveries", tb => tb.HasComment("快递公司配置表,存储快递公司信息")); - - entity.HasIndex(e => e.Code, "ix_deliveries_code"); - - entity.Property(e => e.Id) - .HasComment("主键ID") - .HasColumnName("id"); - entity.Property(e => e.Code) - .HasMaxLength(30) - .HasComment("快递公司编码") - .HasColumnName("code"); - entity.Property(e => e.Name) - .HasMaxLength(50) - .HasComment("快递公司名称") - .HasColumnName("name"); + .HasComment("更新时间"); }); // ==================== 小程序业务表配置 ==================== diff --git a/server/MiAssessment/src/MiAssessment.Model/Entities/Admin.cs b/server/MiAssessment/src/MiAssessment.Model/Entities/Admin.cs deleted file mode 100644 index b1a107b..0000000 --- a/server/MiAssessment/src/MiAssessment.Model/Entities/Admin.cs +++ /dev/null @@ -1,70 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace MiAssessment.Model.Entities; - -/// -/// 管理员表,存储后台管理员信息 -/// -public partial class Admin -{ - /// - /// 主键ID - /// - public int Id { get; set; } - - /// - /// 用户名 - /// - public string? Username { get; set; } - - /// - /// 昵称 - /// - public string Nickname { get; set; } = null!; - - /// - /// 密码(加密) - /// - public string? Password { get; set; } - - /// - /// 权限组ID - /// - public int? Qid { get; set; } - - /// - /// 状态:0-正常 - /// - public int? Status { get; set; } - - /// - /// 获取时间 - /// - public DateTime GetTime { get; set; } - - /// - /// 随机字符串 - /// - public string Random { get; set; } = null!; - - /// - /// 登录令牌 - /// - public string? Token { get; set; } - - /// - /// 上级管理员ID - /// - public int AdminId { get; set; } - - /// - /// 创建时间 - /// - public DateTime CreatedAt { get; set; } - - /// - /// 更新时间 - /// - public DateTime UpdatedAt { get; set; } -} diff --git a/server/MiAssessment/src/MiAssessment.Model/Entities/AdminLoginLog.cs b/server/MiAssessment/src/MiAssessment.Model/Entities/AdminLoginLog.cs deleted file mode 100644 index 8922aab..0000000 --- a/server/MiAssessment/src/MiAssessment.Model/Entities/AdminLoginLog.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace MiAssessment.Model.Entities; - -/// -/// 管理员登录日志表,记录管理员登录信息(仅结构,不迁移历史数据) -/// -public partial class AdminLoginLog -{ - /// - /// 主键ID - /// - public int Id { get; set; } - - /// - /// 管理员ID - /// - public int AdminId { get; set; } - - /// - /// 登录IP地址 - /// - public string Ip { get; set; } = null!; - - /// - /// 登录时间 - /// - public DateTime CreatedAt { get; set; } -} diff --git a/server/MiAssessment/src/MiAssessment.Model/Entities/AdminOperationLog.cs b/server/MiAssessment/src/MiAssessment.Model/Entities/AdminOperationLog.cs deleted file mode 100644 index a11c571..0000000 --- a/server/MiAssessment/src/MiAssessment.Model/Entities/AdminOperationLog.cs +++ /dev/null @@ -1,40 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace MiAssessment.Model.Entities; - -/// -/// 管理员操作日志表,记录管理员操作信息(仅结构,不迁移历史数据) -/// -public partial class AdminOperationLog -{ - /// - /// 主键ID - /// - public int Id { get; set; } - - /// - /// 管理员ID - /// - public int AdminId { get; set; } - - /// - /// 操作IP地址 - /// - public string Ip { get; set; } = null!; - - /// - /// 操作名称 - /// - public string? Operation { get; set; } - - /// - /// 操作内容详情 - /// - public string? Content { get; set; } - - /// - /// 操作时间 - /// - public DateTime CreatedAt { get; set; } -} diff --git a/server/MiAssessment/src/MiAssessment.Model/Entities/Config.cs b/server/MiAssessment/src/MiAssessment.Model/Entities/Config.cs index d987bbf..6504dc9 100644 --- a/server/MiAssessment/src/MiAssessment.Model/Entities/Config.cs +++ b/server/MiAssessment/src/MiAssessment.Model/Entities/Config.cs @@ -1,25 +1,64 @@ using System; -using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; namespace MiAssessment.Model.Entities; /// -/// 系统配置表,存储系统各项配置信息 +/// 业务配置表,存储业务相关配置信息 /// -public partial class Config +[Table("configs")] +public class Config { /// /// 主键ID /// - public int Id { get; set; } + [Key] + public long Id { get; set; } /// /// 配置键名 /// + [Required] + [MaxLength(100)] public string ConfigKey { get; set; } = null!; /// /// 配置值(JSON格式) /// - public string? ConfigValue { get; set; } + [Required] + public string ConfigValue { get; set; } = null!; + + /// + /// 配置类型 + /// + [Required] + [MaxLength(50)] + public string ConfigType { get; set; } = null!; + + /// + /// 描述 + /// + [MaxLength(500)] + public string? Description { get; set; } + + /// + /// 排序 + /// + public int Sort { get; set; } + + /// + /// 创建时间 + /// + public DateTime CreateTime { get; set; } + + /// + /// 更新时间 + /// + public DateTime UpdateTime { get; set; } + + /// + /// 软删除标记 + /// + public bool IsDeleted { get; set; } } diff --git a/server/MiAssessment/src/MiAssessment.Model/Entities/Delivery.cs b/server/MiAssessment/src/MiAssessment.Model/Entities/Delivery.cs deleted file mode 100644 index 8282ee5..0000000 --- a/server/MiAssessment/src/MiAssessment.Model/Entities/Delivery.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace MiAssessment.Model.Entities; - -/// -/// 快递公司配置表,存储快递公司信息 -/// -public partial class Delivery -{ - /// - /// 主键ID - /// - public short Id { get; set; } - - /// - /// 快递公司名称 - /// - public string Name { get; set; } = null!; - - /// - /// 快递公司编码 - /// - public string Code { get; set; } = null!; -} diff --git a/server/MiAssessment/src/MiAssessment.Model/Entities/OrderNotify.cs b/server/MiAssessment/src/MiAssessment.Model/Entities/OrderNotify.cs index 420e46f..d83fdd3 100644 --- a/server/MiAssessment/src/MiAssessment.Model/Entities/OrderNotify.cs +++ b/server/MiAssessment/src/MiAssessment.Model/Entities/OrderNotify.cs @@ -1,35 +1,44 @@ using System; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; namespace MiAssessment.Model.Entities; /// /// 支付通知记录表,记录微信支付回调通知 /// -public partial class OrderNotify +[Table("order_notifies")] +public class OrderNotify { /// /// 主键ID /// - public int Id { get; set; } + [Key] + public long Id { get; set; } /// /// 商户订单号 /// + [Required] + [MaxLength(64)] public string OrderNo { get; set; } = null!; /// /// 微信支付订单号 /// + [MaxLength(64)] public string? TransactionId { get; set; } /// /// 回调通知URL /// + [MaxLength(500)] public string? NotifyUrl { get; set; } /// /// 随机字符串 /// + [MaxLength(64)] public string? NonceStr { get; set; } /// @@ -45,7 +54,7 @@ public partial class OrderNotify /// /// 处理状态:0=待处理,1=处理成功,2=处理失败 /// - public byte Status { get; set; } + public int Status { get; set; } /// /// 重试次数 @@ -55,11 +64,13 @@ public partial class OrderNotify /// /// 附加数据(订单类型) /// + [MaxLength(100)] public string? Attach { get; set; } /// /// 用户OpenId /// + [MaxLength(100)] public string? OpenId { get; set; } /// @@ -70,20 +81,16 @@ public partial class OrderNotify /// /// 错误信息 /// + [MaxLength(500)] public string? ErrorMessage { get; set; } - /// - /// 扩展数据(JSON格式) - /// - public string? Extend { get; set; } - /// /// 创建时间 /// - public DateTime CreatedAt { get; set; } + public DateTime CreateTime { get; set; } /// /// 更新时间 /// - public DateTime UpdatedAt { get; set; } + public DateTime UpdateTime { get; set; } } diff --git a/server/MiAssessment/src/MiAssessment.Model/Entities/PaymentOrder.cs b/server/MiAssessment/src/MiAssessment.Model/Entities/PaymentOrder.cs deleted file mode 100644 index fd56e9c..0000000 --- a/server/MiAssessment/src/MiAssessment.Model/Entities/PaymentOrder.cs +++ /dev/null @@ -1,106 +0,0 @@ -using System; - -namespace MiAssessment.Model.Entities; - -/// -/// 通用支付订单表,支持多种订单类型和奖励发放机制 -/// -public partial class PaymentOrder -{ - /// - /// 主键ID - /// - public int Id { get; set; } - - /// - /// 订单号(唯一) - /// - public string OrderNo { get; set; } = null!; - - /// - /// 用户ID - /// - public int UserId { get; set; } - - /// - /// 订单类型(如:diamond_recharge, vip_purchase 等) - /// - public string OrderType { get; set; } = null!; - - /// - /// 订单标题 - /// - public string Title { get; set; } = null!; - - /// - /// 订单金额(单位:元) - /// - public decimal Amount { get; set; } - - /// - /// 实付金额(单位:元) - /// - public decimal PayAmount { get; set; } - - /// - /// 支付方式(如:wechat, alipay 等) - /// - public string? PayMethod { get; set; } - - /// - /// 状态:0-待支付 1-已支付 2-已取消 3-已退款 - /// - public byte Status { get; set; } - - /// - /// 支付时间 - /// - public DateTime? PaidAt { get; set; } - - /// - /// 第三方交易号 - /// - public string? TransactionId { get; set; } - - /// - /// 业务关联ID - /// - public int? BizId { get; set; } - - /// - /// 业务扩展数据(JSON格式) - /// - public string? BizData { get; set; } - - /// - /// 奖励状态:0-未发放 1-已发放 2-发放失败 - /// - public byte RewardStatus { get; set; } - - /// - /// 奖励数据(JSON格式) - /// - public string? RewardData { get; set; } - - /// - /// 奖励发放时间 - /// - public DateTime? RewardAt { get; set; } - - /// - /// 创建时间 - /// - public DateTime CreatedAt { get; set; } - - /// - /// 更新时间 - /// - public DateTime UpdatedAt { get; set; } - - // ==================== 导航属性 ==================== - - /// - /// 关联的用户 - /// - public virtual User? User { get; set; } -} diff --git a/server/MiAssessment/src/MiAssessment.Model/Entities/Picture.cs b/server/MiAssessment/src/MiAssessment.Model/Entities/Picture.cs deleted file mode 100644 index 0ba363e..0000000 --- a/server/MiAssessment/src/MiAssessment.Model/Entities/Picture.cs +++ /dev/null @@ -1,40 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace MiAssessment.Model.Entities; - -/// -/// 图片管理表,存储上传的图片信息 -/// -public partial class Picture -{ - /// - /// 主键ID - /// - public int Id { get; set; } - - /// - /// 图片URL地址 - /// - public string ImgUrl { get; set; } = null!; - - /// - /// 图片令牌/标识 - /// - public string Token { get; set; } = null!; - - /// - /// 创建时间 - /// - public DateTime CreatedAt { get; set; } - - /// - /// 状态:1-正常 - /// - public byte Status { get; set; } - - /// - /// 图片类型 - /// - public byte? Type { get; set; } -} diff --git a/server/MiAssessment/src/MiAssessment.Model/Entities/User.cs b/server/MiAssessment/src/MiAssessment.Model/Entities/User.cs index a0c58a6..02aaba7 100644 --- a/server/MiAssessment/src/MiAssessment.Model/Entities/User.cs +++ b/server/MiAssessment/src/MiAssessment.Model/Entities/User.cs @@ -1,73 +1,79 @@ using System; -using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; namespace MiAssessment.Model.Entities; /// /// 用户主表,存储用户基本信息 -/// 精简版:只保留核心字段,业务字段移至 UserDetail 扩展表 /// +[Table("users")] public partial class User { /// /// 主键ID /// + [Key] public int Id { get; set; } + /// + /// 用户唯一标识 + /// + [Required] + [MaxLength(6)] + public string Uid { get; set; } = null!; + /// /// 微信openid /// + [Required] + [MaxLength(64)] public string OpenId { get; set; } = null!; /// /// 微信unionid /// + [MaxLength(64)] public string? UnionId { get; set; } /// /// 公众号openid /// + [MaxLength(64)] public string? GzhOpenId { get; set; } - /// - /// 用户唯一标识 - /// - public string Uid { get; set; } = null!; - /// /// 手机号 /// - public string? Mobile { get; set; } + [MaxLength(20)] + public string? Phone { get; set; } /// /// 昵称 /// - public string Nickname { get; set; } = null!; + [MaxLength(50)] + public string? Nickname { get; set; } /// /// 头像URL /// - public string HeadImg { get; set; } = null!; - - /// - /// 密码 - /// - public string? Password { get; set; } - - /// - /// 推荐人ID - /// - public int Pid { get; set; } + [MaxLength(500)] + public string? Avatar { get; set; } /// /// 用户等级:1普通用户 2合伙人 3渠道合伙人 /// public int UserLevel { get; set; } = 1; + /// + /// 推荐人用户ID + /// + public int? ParentUserId { get; set; } + /// /// 用户专属邀请码 /// + [MaxLength(10)] public string? InviteCode { get; set; } /// @@ -91,23 +97,13 @@ public partial class User /// /// 状态: 1正常 0禁用 /// - public byte Status { get; set; } + public int Status { get; set; } /// /// 是否测试账号: 0否 1是 /// public int IsTest { get; set; } - /// - /// 创建时间 - /// - public DateTime CreatedAt { get; set; } - - /// - /// 更新时间 - /// - public DateTime UpdatedAt { get; set; } - /// /// 最后登录时间 /// @@ -116,12 +112,22 @@ public partial class User /// /// 最后登录IP /// + [MaxLength(50)] public string? LastLoginIp { get; set; } - // ==================== 导航属性 ==================== + /// + /// 创建时间 + /// + public DateTime CreateTime { get; set; } /// - /// 用户详情(一对一关联) + /// 更新时间 /// - public virtual UserDetail? UserDetail { get; set; } + public DateTime UpdateTime { get; set; } + + /// + /// 软删除标记 + /// + public bool IsDeleted { get; set; } + } diff --git a/server/MiAssessment/src/MiAssessment.Model/Entities/UserAddress.cs b/server/MiAssessment/src/MiAssessment.Model/Entities/UserAddress.cs deleted file mode 100644 index cc0b9f8..0000000 --- a/server/MiAssessment/src/MiAssessment.Model/Entities/UserAddress.cs +++ /dev/null @@ -1,55 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace MiAssessment.Model.Entities; - -/// -/// 用户收货地址表,存储用户的收货地址信息 -/// -public partial class UserAddress -{ - /// - /// 主键ID - /// - public int Id { get; set; } - - /// - /// 用户ID - /// - public int UserId { get; set; } - - /// - /// 收货人姓名 - /// - public string ReceiverName { get; set; } = null!; - - /// - /// 收货人电话 - /// - public string ReceiverPhone { get; set; } = null!; - - /// - /// 详细地址 - /// - public string DetailedAddress { get; set; } = null!; - - /// - /// 是否默认地址: 0否 1是 - /// - public byte? IsDefault { get; set; } - - /// - /// 是否删除: 0否 1是 - /// - public byte? IsDeleted { get; set; } - - /// - /// 创建时间 - /// - public DateTime CreatedAt { get; set; } - - /// - /// 更新时间 - /// - public DateTime UpdatedAt { get; set; } -} diff --git a/server/MiAssessment/src/MiAssessment.Model/Entities/UserDetail.cs b/server/MiAssessment/src/MiAssessment.Model/Entities/UserDetail.cs deleted file mode 100644 index 02b07c5..0000000 --- a/server/MiAssessment/src/MiAssessment.Model/Entities/UserDetail.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System; - -namespace MiAssessment.Model.Entities; - -/// -/// 用户详情扩展表,用于存储业务扩展字段 -/// 与 User 表一对一关联,按需添加余额、积分、等级等业务字段 -/// -public partial class UserDetail -{ - /// - /// 主键ID - /// - public int Id { get; set; } - - /// - /// 用户ID(唯一,与 User 表一对一关联) - /// - public int UserId { get; set; } - - /// - /// 创建时间 - /// - public DateTime CreatedAt { get; set; } - - /// - /// 更新时间 - /// - public DateTime UpdatedAt { get; set; } - - // ==================== 导航属性 ==================== - - /// - /// 关联的用户 - /// - public virtual User User { get; set; } = null!; -} diff --git a/server/MiAssessment/src/MiAssessment.Model/Entities/UserLoginLog.cs b/server/MiAssessment/src/MiAssessment.Model/Entities/UserLoginLog.cs index c2e9cbf..56e614e 100644 --- a/server/MiAssessment/src/MiAssessment.Model/Entities/UserLoginLog.cs +++ b/server/MiAssessment/src/MiAssessment.Model/Entities/UserLoginLog.cs @@ -1,65 +1,64 @@ using System; -using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; namespace MiAssessment.Model.Entities; /// -/// 用户登录日志表,记录用户每次登录的时间、设备和位置信息 +/// 用户登录日志表,记录用户每次登录信息 /// -public partial class UserLoginLog +[Table("user_login_logs")] +public class UserLoginLog { /// /// 主键ID /// - public int Id { get; set; } + [Key] + public long Id { get; set; } /// /// 用户ID /// - public int UserId { get; set; } + public long UserId { get; set; } /// - /// 登录日期 + /// 登录类型(wechat/mobile/sms等) /// - public DateOnly LoginDate { get; set; } - - /// - /// 登录时间 - /// - public DateTime? LoginTime { get; set; } - - /// - /// 最后登录时间 - /// - public DateTime? LastLoginTime { get; set; } - - /// - /// 设备类型 - /// - public string? Device { get; set; } + [Required] + [MaxLength(20)] + public string LoginType { get; set; } = null!; /// /// 登录IP /// - public string? Ip { get; set; } + [MaxLength(50)] + public string? LoginIp { get; set; } /// - /// 登录位置 + /// 用户代理 /// - public string? Location { get; set; } + [MaxLength(500)] + public string? UserAgent { get; set; } /// - /// 年份 + /// 平台(miniprogram/h5/app等) /// - public int Year { get; set; } + [MaxLength(20)] + public string? Platform { get; set; } /// - /// 月份 + /// 状态:1成功 0失败 /// - public int Month { get; set; } + public int Status { get; set; } /// - /// 周数 + /// 失败原因 /// - public int Week { get; set; } + [MaxLength(200)] + public string? FailReason { get; set; } + + /// + /// 创建时间 + /// + public DateTime CreateTime { get; set; } } diff --git a/server/MiAssessment/src/MiAssessment.Model/Models/Address/AddressModels.cs b/server/MiAssessment/src/MiAssessment.Model/Models/Address/AddressModels.cs deleted file mode 100644 index b1606a0..0000000 --- a/server/MiAssessment/src/MiAssessment.Model/Models/Address/AddressModels.cs +++ /dev/null @@ -1,115 +0,0 @@ -namespace MiAssessment.Model.Models.Address; - -/// -/// 地址响应DTO -/// -public class AddressDto -{ - /// - /// 地址ID - /// - public int Id { get; set; } - - /// - /// 用户ID - /// - public int UserId { get; set; } - - /// - /// 收货人姓名 - /// - public string ReceiverName { get; set; } = string.Empty; - - /// - /// 收货人电话 - /// - public string ReceiverPhone { get; set; } = string.Empty; - - /// - /// 详细地址 - /// - public string DetailedAddress { get; set; } = string.Empty; - - /// - /// 是否默认地址: 0否 1是 - /// - public int IsDefault { get; set; } - - /// - /// 创建时间 - /// - public string? CreateTime { get; set; } - - /// - /// 更新时间 - /// - public string? UpdateTime { get; set; } -} - -/// -/// 添加地址请求 -/// -public class AddAddressRequest -{ - /// - /// 收货人姓名 - /// - public string ReceiverName { get; set; } = string.Empty; - - /// - /// 收货人电话 - /// - public string ReceiverPhone { get; set; } = string.Empty; - - /// - /// 详细地址 - /// - public string DetailedAddress { get; set; } = string.Empty; - - /// - /// 是否设为默认地址: 0否 1是 - /// - public int IsDefault { get; set; } -} - -/// -/// 更新地址请求 -/// -public class UpdateAddressRequest -{ - /// - /// 地址ID - /// - public int Id { get; set; } - - /// - /// 收货人姓名 - /// - public string ReceiverName { get; set; } = string.Empty; - - /// - /// 收货人电话 - /// - public string ReceiverPhone { get; set; } = string.Empty; - - /// - /// 详细地址 - /// - public string DetailedAddress { get; set; } = string.Empty; - - /// - /// 是否设为默认地址: 0否 1是 - /// - public int? IsDefault { get; set; } -} - -/// -/// 地址ID请求 -/// -public class AddressIdRequest -{ - /// - /// 地址ID - /// - public int Id { get; set; } -} diff --git a/server/MiAssessment/src/MiAssessment.Model/Models/Payment/CreatePaymentOrderRequest.cs b/server/MiAssessment/src/MiAssessment.Model/Models/Payment/CreatePaymentOrderRequest.cs deleted file mode 100644 index 0c907f1..0000000 --- a/server/MiAssessment/src/MiAssessment.Model/Models/Payment/CreatePaymentOrderRequest.cs +++ /dev/null @@ -1,47 +0,0 @@ -namespace MiAssessment.Model.Models.Payment; - -/// -/// 创建支付订单请求 -/// -public class CreatePaymentOrderRequest -{ - /// - /// 用户ID - /// - public int UserId { get; set; } - - /// - /// 订单类型(如:diamond_recharge, vip_purchase 等) - /// - public string OrderType { get; set; } = string.Empty; - - /// - /// 订单标题 - /// - public string Title { get; set; } = string.Empty; - - /// - /// 订单金额(单位:元) - /// - public decimal Amount { get; set; } - - /// - /// 实付金额(单位:元),默认等于订单金额 - /// - public decimal? PayAmount { get; set; } - - /// - /// 支付方式(如:wechat, alipay 等) - /// - public string? PayMethod { get; set; } - - /// - /// 业务关联ID - /// - public int? BizId { get; set; } - - /// - /// 业务扩展数据(JSON格式) - /// - public string? BizData { get; set; } -} diff --git a/server/MiAssessment/src/MiAssessment.Model/Models/Payment/PaymentOrderDto.cs b/server/MiAssessment/src/MiAssessment.Model/Models/Payment/PaymentOrderDto.cs deleted file mode 100644 index a19b8fe..0000000 --- a/server/MiAssessment/src/MiAssessment.Model/Models/Payment/PaymentOrderDto.cs +++ /dev/null @@ -1,120 +0,0 @@ -namespace MiAssessment.Model.Models.Payment; - -/// -/// 支付订单DTO -/// -public class PaymentOrderDto -{ - /// - /// 主键ID - /// - public int Id { get; set; } - - /// - /// 订单号 - /// - public string OrderNo { get; set; } = string.Empty; - - /// - /// 用户ID - /// - public int UserId { get; set; } - - /// - /// 订单类型 - /// - public string OrderType { get; set; } = string.Empty; - - /// - /// 订单标题 - /// - public string Title { get; set; } = string.Empty; - - /// - /// 订单金额(单位:元) - /// - public decimal Amount { get; set; } - - /// - /// 实付金额(单位:元) - /// - public decimal PayAmount { get; set; } - - /// - /// 支付方式 - /// - public string? PayMethod { get; set; } - - /// - /// 状态:0-待支付 1-已支付 2-已取消 3-已退款 - /// - public byte Status { get; set; } - - /// - /// 状态文本 - /// - public string StatusText => Status switch - { - 0 => "待支付", - 1 => "已支付", - 2 => "已取消", - 3 => "已退款", - _ => "未知" - }; - - /// - /// 支付时间 - /// - public DateTime? PaidAt { get; set; } - - /// - /// 第三方交易号 - /// - public string? TransactionId { get; set; } - - /// - /// 业务关联ID - /// - public int? BizId { get; set; } - - /// - /// 业务扩展数据(JSON格式) - /// - public string? BizData { get; set; } - - /// - /// 奖励状态:0-未发放 1-已发放 2-发放失败 - /// - public byte RewardStatus { get; set; } - - /// - /// 奖励状态文本 - /// - public string RewardStatusText => RewardStatus switch - { - 0 => "未发放", - 1 => "已发放", - 2 => "发放失败", - _ => "未知" - }; - - /// - /// 奖励数据(JSON格式) - /// - public string? RewardData { get; set; } - - /// - /// 奖励发放时间 - /// - public DateTime? RewardAt { get; set; } - - /// - /// 创建时间 - /// - public DateTime CreatedAt { get; set; } - - /// - /// 更新时间 - /// - public DateTime UpdatedAt { get; set; } -} diff --git a/server/MiAssessment/src/MiAssessment.Model/Models/Payment/PaymentOrderQueryRequest.cs b/server/MiAssessment/src/MiAssessment.Model/Models/Payment/PaymentOrderQueryRequest.cs deleted file mode 100644 index 7189d73..0000000 --- a/server/MiAssessment/src/MiAssessment.Model/Models/Payment/PaymentOrderQueryRequest.cs +++ /dev/null @@ -1,45 +0,0 @@ -using Microsoft.AspNetCore.Mvc; - -namespace MiAssessment.Model.Models.Payment; - -/// -/// 支付订单查询请求 -/// -public class PaymentOrderQueryRequest -{ - /// - /// 页码,从1开始 - /// - [FromQuery(Name = "page")] - public int Page { get; set; } = 1; - - /// - /// 每页数量 - /// - [FromQuery(Name = "pageSize")] - public int PageSize { get; set; } = 10; - - /// - /// 订单类型(可选) - /// - [FromQuery(Name = "orderType")] - public string? OrderType { get; set; } - - /// - /// 订单状态(可选):0-待支付 1-已支付 2-已取消 3-已退款 - /// - [FromQuery(Name = "status")] - public byte? Status { get; set; } - - /// - /// 开始时间(可选) - /// - [FromQuery(Name = "startTime")] - public DateTime? StartTime { get; set; } - - /// - /// 结束时间(可选) - /// - [FromQuery(Name = "endTime")] - public DateTime? EndTime { get; set; } -} diff --git a/server/MiAssessment/tests/MiAssessment.Tests/Core/PaymentOrderServicePropertyTests.cs b/server/MiAssessment/tests/MiAssessment.Tests/Core/PaymentOrderServicePropertyTests.cs deleted file mode 100644 index fb4f815..0000000 --- a/server/MiAssessment/tests/MiAssessment.Tests/Core/PaymentOrderServicePropertyTests.cs +++ /dev/null @@ -1,607 +0,0 @@ -using FsCheck; -using FsCheck.Xunit; -using MiAssessment.Core.Interfaces; -using MiAssessment.Core.Services; -using MiAssessment.Model.Data; -using MiAssessment.Model.Entities; -using MiAssessment.Model.Models.Payment; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Logging; -using Moq; -using Xunit; - -namespace MiAssessment.Tests.Core; - -/// -/// PaymentOrderService 属性测试 -/// Feature: framework-template -/// -/// Property 1: Payment Order Creation Integrity -/// *For any* payment order creation request with valid user ID, order type, and amount, the created order SHALL have: -/// - A unique order number that does not exist in the database -/// - The order_type field correctly set to the requested type -/// - The amount field correctly set to the requested amount -/// - The biz_data field correctly storing the provided JSON data -/// **Validates: Requirements 5.1, 5.2, 5.3** -/// -/// Property 2: Payment Order State Transition -/// *For any* payment order that receives a successful payment callback: -/// - The status SHALL be updated from 0 (pending) to 1 (paid) -/// - The paid_at timestamp SHALL be set -/// - The transaction_id SHALL be recorded -/// - The reward processing SHALL be triggered -/// - If reward succeeds, reward_status SHALL be 1 and reward_at SHALL be set -/// - If reward fails, reward_status SHALL be 2 -/// **Validates: Requirements 5.4, 5.5, 5.6, 5.7** -/// -public class PaymentOrderServicePropertyTests -{ - private readonly Mock> _mockLogger = new(); - - /// - /// 订单状态:待支付 - /// - private const byte StatusPending = 0; - - /// - /// 订单状态:已支付 - /// - private const byte StatusPaid = 1; - - /// - /// 奖励状态:未发放 - /// - private const byte RewardStatusPending = 0; - - /// - /// 奖励状态:已发放 - /// - private const byte RewardStatusSuccess = 1; - - /// - /// 奖励状态:发放失败 - /// - private const byte RewardStatusFailed = 2; - - #region Property 1: Payment Order Creation Integrity - - /// - /// Feature: framework-template, Property 1: Payment Order Creation Integrity - /// - /// Property 1.1: Created order has unique order number - /// A unique order number that does not exist in the database - /// - /// **Validates: Requirements 5.1** - /// - [Property(MaxTest = 100)] - public bool CreateOrder_ShouldGenerateUniqueOrderNo(PositiveInt userId, PositiveInt seed) - { - // Arrange - using var dbContext = CreateDbContext(); - var service = CreateService(dbContext); - - var request1 = CreateValidRequest(userId.Get, seed.Get); - var request2 = CreateValidRequest(userId.Get, seed.Get + 1); - - // Act: Create two orders - var order1 = service.CreateOrderAsync(request1).GetAwaiter().GetResult(); - var order2 = service.CreateOrderAsync(request2).GetAwaiter().GetResult(); - - // Assert: Order numbers should be unique - return order1.OrderNo != order2.OrderNo; - } - - /// - /// Feature: framework-template, Property 1: Payment Order Creation Integrity - /// - /// Property 1.2: Created order has correct order_type - /// The order_type field correctly set to the requested type - /// - /// **Validates: Requirements 5.2** - /// - [Property(MaxTest = 100)] - public bool CreateOrder_ShouldSetCorrectOrderType(PositiveInt userId, NonEmptyString orderType, PositiveInt seed) - { - // Arrange - using var dbContext = CreateDbContext(); - var service = CreateService(dbContext); - - // Generate a valid order type (alphanumeric with underscores) - var validOrderType = GenerateValidOrderType(orderType.Get, seed.Get); - var request = CreateValidRequest(userId.Get, seed.Get); - request.OrderType = validOrderType; - - // Act - var order = service.CreateOrderAsync(request).GetAwaiter().GetResult(); - - // Assert: Order type should match the request - return order.OrderType == validOrderType; - } - - /// - /// Feature: framework-template, Property 1: Payment Order Creation Integrity - /// - /// Property 1.3: Created order has correct amount - /// The amount field correctly set to the requested amount - /// - /// **Validates: Requirements 5.3** - /// - [Property(MaxTest = 100)] - public bool CreateOrder_ShouldSetCorrectAmount(PositiveInt userId, PositiveInt amountCents, PositiveInt seed) - { - // Arrange - using var dbContext = CreateDbContext(); - var service = CreateService(dbContext); - - // Generate a valid amount (convert cents to decimal to avoid floating point issues) - var amount = Math.Round((decimal)(amountCents.Get % 100000 + 1) / 100, 2); - var request = CreateValidRequest(userId.Get, seed.Get); - request.Amount = amount; - - // Act - var order = service.CreateOrderAsync(request).GetAwaiter().GetResult(); - - // Assert: Amount should match the request - return order.Amount == amount; - } - - /// - /// Feature: framework-template, Property 1: Payment Order Creation Integrity - /// - /// Property 1.4: Created order has correct biz_data - /// The biz_data field correctly storing the provided JSON data - /// - /// **Validates: Requirements 5.3** - /// - [Property(MaxTest = 100)] - public bool CreateOrder_ShouldSetCorrectBizData(PositiveInt userId, PositiveInt seed) - { - // Arrange - using var dbContext = CreateDbContext(); - var service = CreateService(dbContext); - - // Generate valid JSON biz_data - var bizData = $"{{\"product_id\":{seed.Get},\"quantity\":{(seed.Get % 10) + 1}}}"; - var request = CreateValidRequest(userId.Get, seed.Get); - request.BizData = bizData; - - // Act - var order = service.CreateOrderAsync(request).GetAwaiter().GetResult(); - - // Assert: BizData should match the request - return order.BizData == bizData; - } - - /// - /// Feature: framework-template, Property 1: Payment Order Creation Integrity - /// - /// Property 1.5: Created order has initial status of pending (0) - /// The created order should have status = 0 (pending) - /// - /// **Validates: Requirements 5.1** - /// - [Property(MaxTest = 100)] - public bool CreateOrder_ShouldHavePendingStatus(PositiveInt userId, PositiveInt seed) - { - // Arrange - using var dbContext = CreateDbContext(); - var service = CreateService(dbContext); - - var request = CreateValidRequest(userId.Get, seed.Get); - - // Act - var order = service.CreateOrderAsync(request).GetAwaiter().GetResult(); - - // Assert: Status should be pending (0) - return order.Status == StatusPending; - } - - /// - /// Feature: framework-template, Property 1: Payment Order Creation Integrity - /// - /// Property 1.6: Created order has initial reward_status of pending (0) - /// The created order should have reward_status = 0 (not issued) - /// - /// **Validates: Requirements 5.1** - /// - [Property(MaxTest = 100)] - public bool CreateOrder_ShouldHavePendingRewardStatus(PositiveInt userId, PositiveInt seed) - { - // Arrange - using var dbContext = CreateDbContext(); - var service = CreateService(dbContext); - - var request = CreateValidRequest(userId.Get, seed.Get); - - // Act - var order = service.CreateOrderAsync(request).GetAwaiter().GetResult(); - - // Assert: RewardStatus should be pending (0) - return order.RewardStatus == RewardStatusPending; - } - - /// - /// Feature: framework-template, Property 1: Payment Order Creation Integrity - /// - /// Property 1.7: Order number does not exist in database before creation - /// A unique order number that does not exist in the database - /// - /// **Validates: Requirements 5.1** - /// - [Property(MaxTest = 100)] - public bool CreateOrder_OrderNoShouldNotExistBeforeCreation(PositiveInt userId, PositiveInt seed) - { - // Arrange - using var dbContext = CreateDbContext(); - var service = CreateService(dbContext); - - var request = CreateValidRequest(userId.Get, seed.Get); - - // Act - var order = service.CreateOrderAsync(request).GetAwaiter().GetResult(); - - // Verify: The order number should exist exactly once in the database - var count = dbContext.PaymentOrders.Count(o => o.OrderNo == order.OrderNo); - - // Assert: Should exist exactly once - return count == 1; - } - - #endregion - - #region Property 2: Payment Order State Transition - - /// - /// Feature: framework-template, Property 2: Payment Order State Transition - /// - /// Property 2.1: Payment success updates status from 0 to 1 - /// The status SHALL be updated from 0 (pending) to 1 (paid) - /// - /// **Validates: Requirements 5.4** - /// - [Property(MaxTest = 100)] - public bool HandlePaymentSuccess_ShouldUpdateStatusToPaid(PositiveInt userId, PositiveInt seed) - { - // Arrange - using var dbContext = CreateDbContext(); - var service = CreateService(dbContext); - - var request = CreateValidRequest(userId.Get, seed.Get); - var order = service.CreateOrderAsync(request).GetAwaiter().GetResult(); - - var transactionId = $"TX_{seed.Get}_{DateTime.Now.Ticks}"; - - // Act - var result = service.HandlePaymentSuccessAsync(order.OrderNo, transactionId, order.Amount).GetAwaiter().GetResult(); - - // Refresh the order from database - var updatedOrder = service.GetOrderByNoAsync(order.OrderNo).GetAwaiter().GetResult(); - - // Assert: Status should be updated to paid (1) - return result && updatedOrder != null && updatedOrder.Status == StatusPaid; - } - - /// - /// Feature: framework-template, Property 2: Payment Order State Transition - /// - /// Property 2.2: Payment success sets paid_at timestamp - /// The paid_at timestamp SHALL be set - /// - /// **Validates: Requirements 5.4** - /// - [Property(MaxTest = 100)] - public bool HandlePaymentSuccess_ShouldSetPaidAtTimestamp(PositiveInt userId, PositiveInt seed) - { - // Arrange - using var dbContext = CreateDbContext(); - var service = CreateService(dbContext); - - var request = CreateValidRequest(userId.Get, seed.Get); - var order = service.CreateOrderAsync(request).GetAwaiter().GetResult(); - - var beforePayment = DateTime.Now.AddSeconds(-1); - var transactionId = $"TX_{seed.Get}_{DateTime.Now.Ticks}"; - - // Act - service.HandlePaymentSuccessAsync(order.OrderNo, transactionId, order.Amount).GetAwaiter().GetResult(); - - // Refresh the order from database - var updatedOrder = service.GetOrderByNoAsync(order.OrderNo).GetAwaiter().GetResult(); - var afterPayment = DateTime.Now.AddSeconds(1); - - // Assert: PaidAt should be set and within reasonable time range - return updatedOrder != null && - updatedOrder.PaidAt.HasValue && - updatedOrder.PaidAt.Value >= beforePayment && - updatedOrder.PaidAt.Value <= afterPayment; - } - - /// - /// Feature: framework-template, Property 2: Payment Order State Transition - /// - /// Property 2.3: Payment success records transaction_id - /// The transaction_id SHALL be recorded - /// - /// **Validates: Requirements 5.4** - /// - [Property(MaxTest = 100)] - public bool HandlePaymentSuccess_ShouldRecordTransactionId(PositiveInt userId, PositiveInt seed) - { - // Arrange - using var dbContext = CreateDbContext(); - var service = CreateService(dbContext); - - var request = CreateValidRequest(userId.Get, seed.Get); - var order = service.CreateOrderAsync(request).GetAwaiter().GetResult(); - - var transactionId = $"TX_{seed.Get}_{DateTime.Now.Ticks}"; - - // Act - service.HandlePaymentSuccessAsync(order.OrderNo, transactionId, order.Amount).GetAwaiter().GetResult(); - - // Refresh the order from database - var updatedOrder = service.GetOrderByNoAsync(order.OrderNo).GetAwaiter().GetResult(); - - // Assert: TransactionId should be recorded - return updatedOrder != null && updatedOrder.TransactionId == transactionId; - } - - /// - /// Feature: framework-template, Property 2: Payment Order State Transition - /// - /// Property 2.4: Payment success with successful reward handler sets reward_status to 1 - /// If reward succeeds, reward_status SHALL be 1 and reward_at SHALL be set - /// - /// **Validates: Requirements 5.5, 5.6** - /// - [Property(MaxTest = 100)] - public bool HandlePaymentSuccess_WithSuccessfulReward_ShouldSetRewardStatusToSuccess(PositiveInt userId, PositiveInt seed) - { - // Arrange - using var dbContext = CreateDbContext(); - - var orderType = $"test_reward_success_{seed.Get % 100}"; - var rewardData = $"{{\"reward_id\":{seed.Get}}}"; - - // Create a mock reward handler that succeeds - var mockHandler = new Mock(); - mockHandler.Setup(h => h.OrderType).Returns(orderType); - mockHandler.Setup(h => h.ProcessRewardAsync(It.IsAny())) - .ReturnsAsync(RewardResult.Ok(rewardData)); - - var service = CreateService(dbContext, new[] { mockHandler.Object }); - - var request = CreateValidRequest(userId.Get, seed.Get); - request.OrderType = orderType; - var order = service.CreateOrderAsync(request).GetAwaiter().GetResult(); - - var transactionId = $"TX_{seed.Get}_{DateTime.Now.Ticks}"; - - // Act - service.HandlePaymentSuccessAsync(order.OrderNo, transactionId, order.Amount).GetAwaiter().GetResult(); - - // Refresh the order from database - var updatedOrder = service.GetOrderByNoAsync(order.OrderNo).GetAwaiter().GetResult(); - - // Assert: RewardStatus should be success (1) and RewardAt should be set - return updatedOrder != null && - updatedOrder.RewardStatus == RewardStatusSuccess && - updatedOrder.RewardAt.HasValue; - } - - /// - /// Feature: framework-template, Property 2: Payment Order State Transition - /// - /// Property 2.5: Payment success with failed reward handler sets reward_status to 2 - /// If reward fails, reward_status SHALL be 2 - /// - /// **Validates: Requirements 5.7** - /// - [Property(MaxTest = 100)] - public bool HandlePaymentSuccess_WithFailedReward_ShouldSetRewardStatusToFailed(PositiveInt userId, PositiveInt seed) - { - // Arrange - using var dbContext = CreateDbContext(); - - var orderType = $"test_reward_fail_{seed.Get % 100}"; - var errorMessage = $"Reward processing failed for seed {seed.Get}"; - - // Create a mock reward handler that fails - var mockHandler = new Mock(); - mockHandler.Setup(h => h.OrderType).Returns(orderType); - mockHandler.Setup(h => h.ProcessRewardAsync(It.IsAny())) - .ReturnsAsync(RewardResult.Fail(errorMessage)); - - var service = CreateService(dbContext, new[] { mockHandler.Object }); - - var request = CreateValidRequest(userId.Get, seed.Get); - request.OrderType = orderType; - var order = service.CreateOrderAsync(request).GetAwaiter().GetResult(); - - var transactionId = $"TX_{seed.Get}_{DateTime.Now.Ticks}"; - - // Act - service.HandlePaymentSuccessAsync(order.OrderNo, transactionId, order.Amount).GetAwaiter().GetResult(); - - // Refresh the order from database - var updatedOrder = service.GetOrderByNoAsync(order.OrderNo).GetAwaiter().GetResult(); - - // Assert: RewardStatus should be failed (2) - return updatedOrder != null && updatedOrder.RewardStatus == RewardStatusFailed; - } - - /// - /// Feature: framework-template, Property 2: Payment Order State Transition - /// - /// Property 2.6: Payment success triggers reward processing - /// The reward processing SHALL be triggered - /// - /// **Validates: Requirements 5.5** - /// - [Property(MaxTest = 100)] - public bool HandlePaymentSuccess_ShouldTriggerRewardProcessing(PositiveInt userId, PositiveInt seed) - { - // Arrange - using var dbContext = CreateDbContext(); - - var orderType = $"test_trigger_{seed.Get % 100}"; - var rewardProcessed = false; - - // Create a mock reward handler that tracks if it was called - var mockHandler = new Mock(); - mockHandler.Setup(h => h.OrderType).Returns(orderType); - mockHandler.Setup(h => h.ProcessRewardAsync(It.IsAny())) - .Callback(() => rewardProcessed = true) - .ReturnsAsync(RewardResult.Ok()); - - var service = CreateService(dbContext, new[] { mockHandler.Object }); - - var request = CreateValidRequest(userId.Get, seed.Get); - request.OrderType = orderType; - var order = service.CreateOrderAsync(request).GetAwaiter().GetResult(); - - var transactionId = $"TX_{seed.Get}_{DateTime.Now.Ticks}"; - - // Act - service.HandlePaymentSuccessAsync(order.OrderNo, transactionId, order.Amount).GetAwaiter().GetResult(); - - // Assert: Reward handler should have been called - return rewardProcessed; - } - - /// - /// Feature: framework-template, Property 2: Payment Order State Transition - /// - /// Property 2.7: Idempotent payment success handling - /// Processing the same payment twice should not change the order state - /// - /// **Validates: Requirements 5.4** - /// - [Property(MaxTest = 100)] - public bool HandlePaymentSuccess_ShouldBeIdempotent(PositiveInt userId, PositiveInt seed) - { - // Arrange - using var dbContext = CreateDbContext(); - var service = CreateService(dbContext); - - var request = CreateValidRequest(userId.Get, seed.Get); - var order = service.CreateOrderAsync(request).GetAwaiter().GetResult(); - - var transactionId = $"TX_{seed.Get}_{DateTime.Now.Ticks}"; - - // Act: Process payment twice - var result1 = service.HandlePaymentSuccessAsync(order.OrderNo, transactionId, order.Amount).GetAwaiter().GetResult(); - var result2 = service.HandlePaymentSuccessAsync(order.OrderNo, transactionId, order.Amount).GetAwaiter().GetResult(); - - // Refresh the order from database - var updatedOrder = service.GetOrderByNoAsync(order.OrderNo).GetAwaiter().GetResult(); - - // Assert: Both calls should succeed and order should be in paid state - return result1 && result2 && updatedOrder != null && updatedOrder.Status == StatusPaid; - } - - /// - /// Feature: framework-template, Property 2: Payment Order State Transition - /// - /// Property 2.8: Successful reward stores reward_data - /// If reward succeeds, reward_data SHALL contain the reward information - /// - /// **Validates: Requirements 5.6** - /// - [Property(MaxTest = 100)] - public bool HandlePaymentSuccess_WithSuccessfulReward_ShouldStoreRewardData(PositiveInt userId, PositiveInt seed) - { - // Arrange - using var dbContext = CreateDbContext(); - - var orderType = $"test_reward_data_{seed.Get % 100}"; - var rewardData = $"{{\"diamonds\":{seed.Get % 1000 + 100},\"bonus\":{seed.Get % 50}}}"; - - // Create a mock reward handler that returns reward data - var mockHandler = new Mock(); - mockHandler.Setup(h => h.OrderType).Returns(orderType); - mockHandler.Setup(h => h.ProcessRewardAsync(It.IsAny())) - .ReturnsAsync(RewardResult.Ok(rewardData)); - - var service = CreateService(dbContext, new[] { mockHandler.Object }); - - var request = CreateValidRequest(userId.Get, seed.Get); - request.OrderType = orderType; - var order = service.CreateOrderAsync(request).GetAwaiter().GetResult(); - - var transactionId = $"TX_{seed.Get}_{DateTime.Now.Ticks}"; - - // Act - service.HandlePaymentSuccessAsync(order.OrderNo, transactionId, order.Amount).GetAwaiter().GetResult(); - - // Refresh the order from database - var updatedOrder = service.GetOrderByNoAsync(order.OrderNo).GetAwaiter().GetResult(); - - // Assert: RewardData should be stored - return updatedOrder != null && updatedOrder.RewardData == rewardData; - } - - #endregion - - #region Helper Methods - - /// - /// 创建内存数据库上下文 - /// - private MiAssessmentDbContext CreateDbContext() - { - var options = new DbContextOptionsBuilder() - .UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString()) - .Options; - - return new MiAssessmentDbContext(options); - } - - /// - /// 创建 PaymentOrderService 实例 - /// - private PaymentOrderService CreateService(MiAssessmentDbContext dbContext, IEnumerable? handlers = null) - { - return new PaymentOrderService( - dbContext, - handlers ?? Array.Empty(), - _mockLogger.Object); - } - - /// - /// 创建有效的支付订单请求 - /// - private CreatePaymentOrderRequest CreateValidRequest(int userId, int seed) - { - return new CreatePaymentOrderRequest - { - UserId = Math.Max(1, userId), // Ensure positive user ID - OrderType = $"test_order_{seed % 100}", - Title = $"测试订单 {seed}", - Amount = Math.Round((decimal)((seed % 10000) + 100) / 100, 2), // 1.00 to 101.00 - PayMethod = "wechat", - BizData = $"{{\"seed\":{seed}}}" - }; - } - - /// - /// 生成有效的订单类型(只包含字母、数字和下划线) - /// - private string GenerateValidOrderType(string input, int seed) - { - // Filter to only alphanumeric and underscore characters - var filtered = new string(input.Where(c => char.IsLetterOrDigit(c) || c == '_').ToArray()); - - // Ensure it's not empty and has reasonable length - if (string.IsNullOrEmpty(filtered)) - { - filtered = "order"; - } - - // Limit length and add seed for uniqueness - var maxLength = Math.Min(filtered.Length, 20); - return $"{filtered.Substring(0, maxLength)}_{seed % 1000}"; - } - - #endregion -} diff --git a/server/MiAssessment/tests/MiAssessment.Tests/Core/PaymentRewardDispatcherPropertyTests.cs b/server/MiAssessment/tests/MiAssessment.Tests/Core/PaymentRewardDispatcherPropertyTests.cs deleted file mode 100644 index 5444fd2..0000000 --- a/server/MiAssessment/tests/MiAssessment.Tests/Core/PaymentRewardDispatcherPropertyTests.cs +++ /dev/null @@ -1,540 +0,0 @@ -using FsCheck; -using FsCheck.Xunit; -using MiAssessment.Core.Interfaces; -using MiAssessment.Core.Services; -using MiAssessment.Model.Entities; -using Microsoft.Extensions.Logging; -using Moq; -using Xunit; - -namespace MiAssessment.Tests.Core; - -/// -/// PaymentRewardDispatcher 属性测试 -/// Feature: framework-template -/// -/// Property 5: Reward Handler Dispatch -/// *For any* payment order with a specific order_type: -/// - The system SHALL search for a registered IPaymentRewardHandler with matching OrderType -/// - If a handler is found, its ProcessRewardAsync method SHALL be called with the order -/// - The handler's RewardResult SHALL determine the order's reward_status and reward_data -/// -/// **Validates: Requirements 6.2, 6.3** -/// -public class PaymentRewardDispatcherPropertyTests -{ - private readonly Mock> _mockLogger = new(); - - #region Property 5: Reward Handler Dispatch - - /// - /// Feature: framework-template, Property 5: Reward Handler Dispatch - /// - /// Property 5.1: Handler found for order type → handler's ProcessRewardAsync is called - /// If a handler is found, its ProcessRewardAsync method SHALL be called with the order - /// - /// **Validates: Requirements 6.2, 6.3** - /// - [Property(MaxTest = 100)] - public bool ProcessReward_WithMatchingHandler_ShouldCallProcessRewardAsync(PositiveInt seed) - { - // Arrange - var orderType = GenerateValidOrderType(seed.Get); - var handlerCalled = false; - PaymentOrder? receivedOrder = null; - - var mockHandler = new Mock(); - mockHandler.Setup(h => h.OrderType).Returns(orderType); - mockHandler.Setup(h => h.ProcessRewardAsync(It.IsAny())) - .Callback(order => - { - handlerCalled = true; - receivedOrder = order; - }) - .ReturnsAsync(RewardResult.Ok()); - - var dispatcher = CreateDispatcher(new[] { mockHandler.Object }); - var paymentOrder = CreatePaymentOrder(seed.Get, orderType); - - // Act - dispatcher.ProcessRewardAsync(paymentOrder).GetAwaiter().GetResult(); - - // Assert: Handler should be called with the correct order - return handlerCalled && receivedOrder != null && receivedOrder.OrderNo == paymentOrder.OrderNo; - } - - /// - /// Feature: framework-template, Property 5: Reward Handler Dispatch - /// - /// Property 5.2: Handler not found → returns success with no reward data - /// If no handler is found for the order type, the system should return success (not failure) - /// - /// **Validates: Requirements 6.2** - /// - [Property(MaxTest = 100)] - public bool ProcessReward_WithNoMatchingHandler_ShouldReturnSuccessWithNoRewardData(PositiveInt seed) - { - // Arrange - var orderType = GenerateValidOrderType(seed.Get); - var differentOrderType = $"different_{orderType}"; - - // Create a handler for a different order type - var mockHandler = new Mock(); - mockHandler.Setup(h => h.OrderType).Returns(differentOrderType); - mockHandler.Setup(h => h.ProcessRewardAsync(It.IsAny())) - .ReturnsAsync(RewardResult.Ok("should_not_be_called")); - - var dispatcher = CreateDispatcher(new[] { mockHandler.Object }); - var paymentOrder = CreatePaymentOrder(seed.Get, orderType); - - // Act - var result = dispatcher.ProcessRewardAsync(paymentOrder).GetAwaiter().GetResult(); - - // Assert: Should return success with no reward data - return result.Success && result.RewardData == null; - } - - /// - /// Feature: framework-template, Property 5: Reward Handler Dispatch - /// - /// Property 5.3: Handler returns success → result contains reward data - /// The handler's RewardResult SHALL determine the order's reward_data - /// - /// **Validates: Requirements 6.3** - /// - [Property(MaxTest = 100)] - public bool ProcessReward_WithSuccessfulHandler_ShouldReturnRewardData(PositiveInt seed) - { - // Arrange - var orderType = GenerateValidOrderType(seed.Get); - var expectedRewardData = $"{{\"diamonds\":{seed.Get % 1000 + 100},\"bonus\":{seed.Get % 50}}}"; - - var mockHandler = new Mock(); - mockHandler.Setup(h => h.OrderType).Returns(orderType); - mockHandler.Setup(h => h.ProcessRewardAsync(It.IsAny())) - .ReturnsAsync(RewardResult.Ok(expectedRewardData)); - - var dispatcher = CreateDispatcher(new[] { mockHandler.Object }); - var paymentOrder = CreatePaymentOrder(seed.Get, orderType); - - // Act - var result = dispatcher.ProcessRewardAsync(paymentOrder).GetAwaiter().GetResult(); - - // Assert: Result should contain the reward data from handler - return result.Success && result.RewardData == expectedRewardData; - } - - /// - /// Feature: framework-template, Property 5: Reward Handler Dispatch - /// - /// Property 5.4: Handler returns failure → result contains error message - /// The handler's RewardResult SHALL determine the order's reward_status - /// - /// **Validates: Requirements 6.3** - /// - [Property(MaxTest = 100)] - public bool ProcessReward_WithFailedHandler_ShouldReturnErrorMessage(PositiveInt seed) - { - // Arrange - var orderType = GenerateValidOrderType(seed.Get); - var expectedErrorMessage = $"Reward processing failed for seed {seed.Get}"; - - var mockHandler = new Mock(); - mockHandler.Setup(h => h.OrderType).Returns(orderType); - mockHandler.Setup(h => h.ProcessRewardAsync(It.IsAny())) - .ReturnsAsync(RewardResult.Fail(expectedErrorMessage)); - - var dispatcher = CreateDispatcher(new[] { mockHandler.Object }); - var paymentOrder = CreatePaymentOrder(seed.Get, orderType); - - // Act - var result = dispatcher.ProcessRewardAsync(paymentOrder).GetAwaiter().GetResult(); - - // Assert: Result should contain the error message from handler - return !result.Success && result.Message == expectedErrorMessage; - } - - /// - /// Feature: framework-template, Property 5: Reward Handler Dispatch - /// - /// Property 5.5: Handler throws exception → result contains error message - /// If the handler throws an exception, the dispatcher should catch it and return failure - /// - /// **Validates: Requirements 6.3** - /// - [Property(MaxTest = 100)] - public bool ProcessReward_WithExceptionThrowingHandler_ShouldReturnErrorMessage(PositiveInt seed) - { - // Arrange - var orderType = GenerateValidOrderType(seed.Get); - var exceptionMessage = $"Unexpected error for seed {seed.Get}"; - - var mockHandler = new Mock(); - mockHandler.Setup(h => h.OrderType).Returns(orderType); - mockHandler.Setup(h => h.ProcessRewardAsync(It.IsAny())) - .ThrowsAsync(new InvalidOperationException(exceptionMessage)); - - var dispatcher = CreateDispatcher(new[] { mockHandler.Object }); - var paymentOrder = CreatePaymentOrder(seed.Get, orderType); - - // Act - var result = dispatcher.ProcessRewardAsync(paymentOrder).GetAwaiter().GetResult(); - - // Assert: Result should be failure and contain error message - return !result.Success && result.Message != null && result.Message.Contains(exceptionMessage); - } - - /// - /// Feature: framework-template, Property 5: Reward Handler Dispatch - /// - /// Property 5.6: Multiple handlers registered → correct handler is selected - /// The system SHALL search for a registered IPaymentRewardHandler with matching OrderType - /// - /// **Validates: Requirements 6.2** - /// - [Property(MaxTest = 100)] - public bool ProcessReward_WithMultipleHandlers_ShouldSelectCorrectHandler(PositiveInt seed) - { - // Arrange - var targetOrderType = GenerateValidOrderType(seed.Get); - var otherOrderType1 = $"other1_{seed.Get}"; - var otherOrderType2 = $"other2_{seed.Get}"; - - var targetRewardData = $"{{\"target_reward\":{seed.Get}}}"; - var targetHandlerCalled = false; - var otherHandler1Called = false; - var otherHandler2Called = false; - - // Create target handler - var targetHandler = new Mock(); - targetHandler.Setup(h => h.OrderType).Returns(targetOrderType); - targetHandler.Setup(h => h.ProcessRewardAsync(It.IsAny())) - .Callback(() => targetHandlerCalled = true) - .ReturnsAsync(RewardResult.Ok(targetRewardData)); - - // Create other handlers - var otherHandler1 = new Mock(); - otherHandler1.Setup(h => h.OrderType).Returns(otherOrderType1); - otherHandler1.Setup(h => h.ProcessRewardAsync(It.IsAny())) - .Callback(() => otherHandler1Called = true) - .ReturnsAsync(RewardResult.Ok("other1_data")); - - var otherHandler2 = new Mock(); - otherHandler2.Setup(h => h.OrderType).Returns(otherOrderType2); - otherHandler2.Setup(h => h.ProcessRewardAsync(It.IsAny())) - .Callback(() => otherHandler2Called = true) - .ReturnsAsync(RewardResult.Ok("other2_data")); - - var dispatcher = CreateDispatcher(new[] - { - otherHandler1.Object, - targetHandler.Object, - otherHandler2.Object - }); - - var paymentOrder = CreatePaymentOrder(seed.Get, targetOrderType); - - // Act - var result = dispatcher.ProcessRewardAsync(paymentOrder).GetAwaiter().GetResult(); - - // Assert: Only target handler should be called, result should match target handler's output - return targetHandlerCalled && - !otherHandler1Called && - !otherHandler2Called && - result.Success && - result.RewardData == targetRewardData; - } - - /// - /// Feature: framework-template, Property 5: Reward Handler Dispatch - /// - /// Property 5.7: GetHandler returns correct handler for registered order type - /// The system SHALL search for a registered IPaymentRewardHandler with matching OrderType - /// - /// **Validates: Requirements 6.2** - /// - [Property(MaxTest = 100)] - public bool GetHandler_WithRegisteredOrderType_ShouldReturnCorrectHandler(PositiveInt seed) - { - // Arrange - var orderType = GenerateValidOrderType(seed.Get); - - var mockHandler = new Mock(); - mockHandler.Setup(h => h.OrderType).Returns(orderType); - - var dispatcher = CreateDispatcher(new[] { mockHandler.Object }); - - // Act - var handler = dispatcher.GetHandler(orderType); - - // Assert: Should return the registered handler - return handler != null && handler.OrderType == orderType; - } - - /// - /// Feature: framework-template, Property 5: Reward Handler Dispatch - /// - /// Property 5.8: GetHandler returns null for unregistered order type - /// If no handler is found, GetHandler should return null - /// - /// **Validates: Requirements 6.2** - /// - [Property(MaxTest = 100)] - public bool GetHandler_WithUnregisteredOrderType_ShouldReturnNull(PositiveInt seed) - { - // Arrange - var registeredOrderType = GenerateValidOrderType(seed.Get); - var unregisteredOrderType = $"unregistered_{seed.Get}"; - - var mockHandler = new Mock(); - mockHandler.Setup(h => h.OrderType).Returns(registeredOrderType); - - var dispatcher = CreateDispatcher(new[] { mockHandler.Object }); - - // Act - var handler = dispatcher.GetHandler(unregisteredOrderType); - - // Assert: Should return null for unregistered order type - return handler == null; - } - - /// - /// Feature: framework-template, Property 5: Reward Handler Dispatch - /// - /// Property 5.9: HasHandler returns true for registered order type - /// The system should correctly identify if a handler exists for an order type - /// - /// **Validates: Requirements 6.2** - /// - [Property(MaxTest = 100)] - public bool HasHandler_WithRegisteredOrderType_ShouldReturnTrue(PositiveInt seed) - { - // Arrange - var orderType = GenerateValidOrderType(seed.Get); - - var mockHandler = new Mock(); - mockHandler.Setup(h => h.OrderType).Returns(orderType); - - var dispatcher = CreateDispatcher(new[] { mockHandler.Object }); - - // Act - var hasHandler = dispatcher.HasHandler(orderType); - - // Assert: Should return true for registered order type - return hasHandler; - } - - /// - /// Feature: framework-template, Property 5: Reward Handler Dispatch - /// - /// Property 5.10: HasHandler returns false for unregistered order type - /// The system should correctly identify if no handler exists for an order type - /// - /// **Validates: Requirements 6.2** - /// - [Property(MaxTest = 100)] - public bool HasHandler_WithUnregisteredOrderType_ShouldReturnFalse(PositiveInt seed) - { - // Arrange - var registeredOrderType = GenerateValidOrderType(seed.Get); - var unregisteredOrderType = $"unregistered_{seed.Get}"; - - var mockHandler = new Mock(); - mockHandler.Setup(h => h.OrderType).Returns(registeredOrderType); - - var dispatcher = CreateDispatcher(new[] { mockHandler.Object }); - - // Act - var hasHandler = dispatcher.HasHandler(unregisteredOrderType); - - // Assert: Should return false for unregistered order type - return !hasHandler; - } - - /// - /// Feature: framework-template, Property 5: Reward Handler Dispatch - /// - /// Property 5.11: GetRegisteredOrderTypes returns all registered order types - /// The system should track all registered handlers - /// - /// **Validates: Requirements 6.2** - /// - [Property(MaxTest = 100)] - public bool GetRegisteredOrderTypes_ShouldReturnAllRegisteredTypes(PositiveInt seed) - { - // Arrange - var orderType1 = $"type1_{seed.Get}"; - var orderType2 = $"type2_{seed.Get}"; - var orderType3 = $"type3_{seed.Get}"; - - var handler1 = new Mock(); - handler1.Setup(h => h.OrderType).Returns(orderType1); - - var handler2 = new Mock(); - handler2.Setup(h => h.OrderType).Returns(orderType2); - - var handler3 = new Mock(); - handler3.Setup(h => h.OrderType).Returns(orderType3); - - var dispatcher = CreateDispatcher(new[] - { - handler1.Object, - handler2.Object, - handler3.Object - }); - - // Act - var registeredTypes = dispatcher.GetRegisteredOrderTypes(); - - // Assert: Should contain all registered order types - return registeredTypes.Count == 3 && - registeredTypes.Contains(orderType1) && - registeredTypes.Contains(orderType2) && - registeredTypes.Contains(orderType3); - } - - /// - /// Feature: framework-template, Property 5: Reward Handler Dispatch - /// - /// Property 5.12: ProcessReward with null order returns failure - /// The dispatcher should handle null orders gracefully - /// - /// **Validates: Requirements 6.3** - /// - [Fact] - public void ProcessReward_WithNullOrder_ShouldReturnFailure() - { - // Arrange - var dispatcher = CreateDispatcher(Array.Empty()); - - // Act - var result = dispatcher.ProcessRewardAsync(null!).GetAwaiter().GetResult(); - - // Assert: Should return failure - Assert.False(result.Success); - Assert.NotNull(result.Message); - } - - /// - /// Feature: framework-template, Property 5: Reward Handler Dispatch - /// - /// Property 5.13: ProcessReward with empty order type returns failure - /// The dispatcher should handle orders with empty order type gracefully - /// - /// **Validates: Requirements 6.3** - /// - [Property(MaxTest = 100)] - public bool ProcessReward_WithEmptyOrderType_ShouldReturnFailure(PositiveInt seed) - { - // Arrange - var dispatcher = CreateDispatcher(Array.Empty()); - var paymentOrder = CreatePaymentOrder(seed.Get, ""); - - // Act - var result = dispatcher.ProcessRewardAsync(paymentOrder).GetAwaiter().GetResult(); - - // Assert: Should return failure - return !result.Success && result.Message != null; - } - - /// - /// Feature: framework-template, Property 5: Reward Handler Dispatch - /// - /// Property 5.14: Order type matching is case-insensitive - /// The system should match order types regardless of case - /// - /// **Validates: Requirements 6.2** - /// - [Property(MaxTest = 100)] - public bool ProcessReward_OrderTypeMatching_ShouldBeCaseInsensitive(PositiveInt seed) - { - // Arrange - var lowerCaseOrderType = $"order_type_{seed.Get}".ToLowerInvariant(); - var upperCaseOrderType = lowerCaseOrderType.ToUpperInvariant(); - var expectedRewardData = $"{{\"reward\":{seed.Get}}}"; - - var mockHandler = new Mock(); - mockHandler.Setup(h => h.OrderType).Returns(lowerCaseOrderType); - mockHandler.Setup(h => h.ProcessRewardAsync(It.IsAny())) - .ReturnsAsync(RewardResult.Ok(expectedRewardData)); - - var dispatcher = CreateDispatcher(new[] { mockHandler.Object }); - var paymentOrder = CreatePaymentOrder(seed.Get, upperCaseOrderType); - - // Act - var result = dispatcher.ProcessRewardAsync(paymentOrder).GetAwaiter().GetResult(); - - // Assert: Should find handler despite case difference - return result.Success && result.RewardData == expectedRewardData; - } - - /// - /// Feature: framework-template, Property 5: Reward Handler Dispatch - /// - /// Property 5.15: Empty handlers collection returns success for any order - /// When no handlers are registered, processing should succeed with no reward - /// - /// **Validates: Requirements 6.2** - /// - [Property(MaxTest = 100)] - public bool ProcessReward_WithNoHandlers_ShouldReturnSuccess(PositiveInt seed) - { - // Arrange - var dispatcher = CreateDispatcher(Array.Empty()); - var paymentOrder = CreatePaymentOrder(seed.Get, GenerateValidOrderType(seed.Get)); - - // Act - var result = dispatcher.ProcessRewardAsync(paymentOrder).GetAwaiter().GetResult(); - - // Assert: Should return success with no reward data - return result.Success && result.RewardData == null; - } - - #endregion - - #region Helper Methods - - /// - /// 创建 PaymentRewardDispatcher 实例 - /// - private PaymentRewardDispatcher CreateDispatcher(IEnumerable handlers) - { - return new PaymentRewardDispatcher(handlers, _mockLogger.Object); - } - - /// - /// 创建测试用的 PaymentOrder - /// - private PaymentOrder CreatePaymentOrder(int seed, string orderType) - { - return new PaymentOrder - { - Id = seed, - OrderNo = $"PO{DateTime.Now:yyyyMMddHHmmss}{seed:D6}", - UserId = Math.Max(1, seed % 10000), - OrderType = orderType, - Title = $"测试订单 {seed}", - Amount = Math.Round((decimal)((seed % 10000) + 100) / 100, 2), - PayAmount = Math.Round((decimal)((seed % 10000) + 100) / 100, 2), - PayMethod = "wechat", - Status = 1, // 已支付 - PaidAt = DateTime.Now, - TransactionId = $"TX_{seed}", - BizData = $"{{\"seed\":{seed}}}", - RewardStatus = 0, - CreatedAt = DateTime.Now, - UpdatedAt = DateTime.Now - }; - } - - /// - /// 生成有效的订单类型 - /// - private string GenerateValidOrderType(int seed) - { - var types = new[] { "diamond_recharge", "vip_purchase", "gift_buy", "subscription", "premium" }; - return $"{types[seed % types.Length]}_{seed % 1000}"; - } - - #endregion -} diff --git a/server/MiAssessment/tests/MiAssessment.Tests/Services/AuthServiceLoginRecordPropertyTests.cs b/server/MiAssessment/tests/MiAssessment.Tests/Services/AuthServiceLoginRecordPropertyTests.cs index 6a903ae..df61783 100644 --- a/server/MiAssessment/tests/MiAssessment.Tests/Services/AuthServiceLoginRecordPropertyTests.cs +++ b/server/MiAssessment/tests/MiAssessment.Tests/Services/AuthServiceLoginRecordPropertyTests.cs @@ -87,28 +87,18 @@ public class AuthServiceLoginRecordPropertyTests Adcode = "110000" }); - // Create a user + // 创建用户 var user = new User { OpenId = "openid_" + Guid.NewGuid().ToString().Substring(0, 8), Uid = "uid123", Nickname = "TestUser", - HeadImg = "https://example.com/avatar.jpg", + Avatar = "https://example.com/avatar.jpg", Status = 1, - CreatedAt = DateTime.UtcNow, - UpdatedAt = DateTime.UtcNow + CreateTime = DateTime.Now, + UpdateTime = DateTime.Now }; await dbContext.Users.AddAsync(user); - - // Create UserAccount for the user - var userAccount = new UserAccount - { - UserId = user.Id, - AccountToken = "token123", - TokenNum = "num123", - LastLoginIp = string.Empty - }; - await dbContext.UserAccounts.AddAsync(userAccount); await dbContext.SaveChangesAsync(); var device = "iOS"; @@ -118,17 +108,15 @@ public class AuthServiceLoginRecordPropertyTests await authService.RecordLoginAsync(user.Id, device, clientIp); // Assert - // 1. UserLoginLog should be created + // 1. UserLoginLog 应该被创建 var loginLog = await dbContext.UserLoginLogs.FirstOrDefaultAsync(l => l.UserId == user.Id); - var logCreated = loginLog != null && loginLog.Device == device; + var logCreated = loginLog != null && loginLog.UserAgent == device; - // 2. UserAccount should be updated - var updatedAccount = await dbContext.UserAccounts.FirstOrDefaultAsync(ua => ua.UserId == user.Id); - var accountUpdated = updatedAccount != null && - updatedAccount.LastLoginTime != null && - updatedAccount.IpProvince == "北京"; + // 2. 用户最后登录时间应该被更新 + var updatedUser = await dbContext.Users.FirstOrDefaultAsync(u => u.Id == user.Id); + var userUpdated = updatedUser != null && updatedUser.LastLoginTime != null; - return logCreated && accountUpdated; + return logCreated && userUpdated; } /// @@ -153,16 +141,16 @@ public class AuthServiceLoginRecordPropertyTests var expectedNickname = "TestUser_" + Random.Shared.Next(1000, 9999); var expectedHeadimg = "https://example.com/avatar_" + Random.Shared.Next(1000, 9999) + ".jpg"; - // Create a user + // 创建用户 var user = new User { OpenId = "openid_" + Guid.NewGuid().ToString().Substring(0, 8), Uid = expectedUid, Nickname = expectedNickname, - HeadImg = expectedHeadimg, + Avatar = expectedHeadimg, Status = 1, - CreatedAt = DateTime.UtcNow, - UpdatedAt = DateTime.UtcNow + CreateTime = DateTime.Now, + UpdateTime = DateTime.Now }; await dbContext.Users.AddAsync(user); await dbContext.SaveChangesAsync(); @@ -197,10 +185,10 @@ public class AuthServiceLoginRecordPropertyTests OpenId = "openid_" + Guid.NewGuid().ToString().Substring(0, 8), Uid = "uid123", Nickname = "TestUser", - HeadImg = "https://example.com/avatar.jpg", + Avatar = "https://example.com/avatar.jpg", Status = 1, // Active - CreatedAt = DateTime.UtcNow, - UpdatedAt = DateTime.UtcNow + CreateTime = DateTime.Now, + UpdateTime = DateTime.Now }; await dbContext1.Users.AddAsync(user1); await dbContext1.SaveChangesAsync(); @@ -221,10 +209,10 @@ public class AuthServiceLoginRecordPropertyTests OpenId = "openid_" + Guid.NewGuid().ToString().Substring(0, 8), Uid = "uid456", Nickname = "TestUser2", - HeadImg = "https://example.com/avatar.jpg", + Avatar = "https://example.com/avatar.jpg", Status = 0, // Inactive - CreatedAt = DateTime.UtcNow, - UpdatedAt = DateTime.UtcNow + CreateTime = DateTime.Now, + UpdateTime = DateTime.Now }; await dbContext2.Users.AddAsync(user2); await dbContext2.SaveChangesAsync();