using Asp.Versioning; using MAX.Framework.Commons; using MAX.Framework.Dtos; using MAX.Framework.Security; using Microsoft.AspNetCore.Mvc; using Store.Framework.Base; using Store.Modules.CloudProduct.Dtos; using Store.Modules.CloudProduct.Services; namespace Store.Modules.CloudProduct.Controllers { /// /// 用户收藏商品:收藏/取消收藏(数据库 + 收藏索引同步)、收藏分组、收藏状态查询、收藏检索与索引重建。 /// 请求中的 userName 允许为空,为空时取当前登录用户。 /// [ApiVersionNeutral] [Route("api/CloudGoods/[action]")] public class UserFavoritesController : AppControllerBase { private readonly IUserFavoritesService _favoritesService; private readonly ICurrentUser _currentUser; public UserFavoritesController(IUserFavoritesService favoritesService, ICurrentUser currentUser) { _favoritesService = favoritesService; _currentUser = currentUser; } /// /// 批量收藏商品:写入 user_goods_favorites 并同步写入收藏索引,返回本次成功收藏的商品数量。 /// 商品已收藏时视为修改收藏分组;商品不存在/已删除的编号会被跳过。 /// [HttpPost] public async Task AddFavorite(AddUserFavoriteRequest request) { if (request == null) { throw new AppException("请求参数不能为空", ResultCode.ValidationError); } request.UserName = ResolveUserName(request.UserName); if (string.IsNullOrWhiteSpace(request.UserName)) { throw new AppException("用户名不能为空", ResultCode.ValidationError); } if (request.GoodsIds == null || request.GoodsIds.Count == 0) { throw new AppException("请提供要收藏的商品编号", ResultCode.ValidationError); } if (request.GroupId < 0) { throw new AppException("收藏分组无效", ResultCode.ValidationError); } return await _favoritesService.AddFavoritesAsync(request); } /// /// 批量取消收藏:删除 user_goods_favorites 记录并同步删除收藏索引文档,返回实际取消的条数。 /// [HttpPost] public async Task RemoveFavorites(RemoveUserFavoritesRequest request) { if (request == null) { throw new AppException("请求参数不能为空", ResultCode.ValidationError); } request.UserName = ResolveUserName(request.UserName); if (string.IsNullOrWhiteSpace(request.UserName)) { throw new AppException("用户名不能为空", ResultCode.ValidationError); } if (request.GoodsIds == null || request.GoodsIds.Count == 0) { throw new AppException("请提供要取消收藏的商品编号", ResultCode.ValidationError); } return await _favoritesService.RemoveFavoritesAsync(request); } /// /// 创建收藏分组:创建成功返回 true,同名分组已存在返回 false。 /// [HttpPost] public async Task CreateGroup(CreateUserFavoriteGroupRequest request) { if (request == null) { throw new AppException("请求参数不能为空", ResultCode.ValidationError); } request.UserName = ResolveUserName(request.UserName); if (string.IsNullOrWhiteSpace(request.UserName)) { throw new AppException("用户名不能为空", ResultCode.ValidationError); } if (string.IsNullOrWhiteSpace(request.GroupName)) { throw new AppException("分组名不能为空", ResultCode.ValidationError); } return await _favoritesService.CreateGroupAsync(request); } /// /// 查询用户的收藏分组(含默认分组)及分组下收藏数量。 /// [HttpGet] public async Task> GetGroups(string? userName = null) { var resolved = ResolveUserName(userName); if (string.IsNullOrWhiteSpace(resolved)) { throw new AppException("用户名不能为空", ResultCode.ValidationError); } return await _favoritesService.GetGroupsAsync(resolved); } /// /// 按 GoodsIds 顺序返回每个商品是否已被收藏(如 [true,false,...]),前台可按顺序直接渲染。 /// [HttpPost] public async Task> GetFavoriteStatus(UserFavoriteStatusRequest request) { if (request == null) { throw new AppException("请求参数不能为空", ResultCode.ValidationError); } request.UserName = ResolveUserName(request.UserName); if (string.IsNullOrWhiteSpace(request.UserName)) { throw new AppException("用户名不能为空", ResultCode.ValidationError); } return await _favoritesService.GetFavoriteStatusAsync(request); } /// /// 检索用户收藏的商品(标题、销量、价格、类目、分组)。 /// [HttpPost] public async Task> SearchFavorites(UserFavoritesSearchRequest request) { // 1. 参数基础校验 if (request == null) { throw new AppException("请求参数不能为空", ResultCode.ValidationError); } request.UserName = ResolveUserName(request.UserName); if (string.IsNullOrWhiteSpace(request.UserName)) { throw new AppException("用户名不能为空", ResultCode.ValidationError); } // 2. 分页参数校验 if (request.PageIndex < 1) { throw new AppException("页码必须大于0", ResultCode.ValidationError); } if (request.PageSize < 1 || request.PageSize > 2000) { throw new AppException("每页数量必须在1-2000之间", ResultCode.ValidationError); } // 3. 价格区间校验 if (request.MinPrice.HasValue && request.MinPrice < 0) { throw new AppException("最低价格不能小于0", ResultCode.ValidationError); } if (request.MaxPrice.HasValue && request.MaxPrice < 0) { throw new AppException("最高价格不能小于0", ResultCode.ValidationError); } if (request.MinPrice.HasValue && request.MaxPrice.HasValue && request.MinPrice > request.MaxPrice) { throw new AppException("最低价格不能大于最高价格", ResultCode.ValidationError); } // 4. 销量区间校验 if (request.MinSoldQuantity.HasValue && request.MinSoldQuantity < 0) { throw new AppException("最低销量不能小于0", ResultCode.ValidationError); } if (request.MaxSoldQuantity.HasValue && request.MaxSoldQuantity < 0) { throw new AppException("最高销量不能小于0", ResultCode.ValidationError); } if (request.MinSoldQuantity.HasValue && request.MaxSoldQuantity.HasValue && request.MinSoldQuantity > request.MaxSoldQuantity) { throw new AppException("最低销量不能大于最高销量", ResultCode.ValidationError); } // 5. 类目层级校验 if (request.CatId2.HasValue && !request.CatId1.HasValue) { throw new AppException("选择二级类目时必须先选择一级类目", ResultCode.ValidationError); } if (request.CatId3.HasValue && !request.CatId2.HasValue) { throw new AppException("选择三级类目时必须先选择二级类目", ResultCode.ValidationError); } if (request.CatId4.HasValue && !request.CatId3.HasValue) { throw new AppException("选择四级类目时必须先选择三级类目", ResultCode.ValidationError); } // 6. 分组校验 if (request.GroupId.HasValue && request.GroupId < 0) { throw new AppException("收藏分组无效", ResultCode.ValidationError); } // 7. 平台校验 if (request.Platform.HasValue && request.Platform < 0) { throw new AppException("平台类型无效", ResultCode.ValidationError); } var result = await _favoritesService.SearchFavoritesAsync(request); return new PageData { List = result.List, Total = result.Total, PageSize = request.PageSize, PageIndex = request.PageIndex, }; } /// /// 移除所有用户收藏索引并重新创建,随后按 user_goods_favorites 关联 goods 全量回填。 /// [HttpPost] public async Task RebuildSearchIndex() { return await _favoritesService.RebuildSearchIndexAsync(); } /// /// 取用户名:显式传入优先,否则取当前登录用户。 /// private string ResolveUserName(string? userName) { return string.IsNullOrWhiteSpace(userName) ? (_currentUser.UserName ?? string.Empty) : userName.Trim(); } } }