diff --git a/.kiro/specs/goods-system-migration/tasks.md b/.kiro/specs/goods-system-migration/tasks.md index 01d5a100..a980ee9e 100644 --- a/.kiro/specs/goods-system-migration/tasks.md +++ b/.kiro/specs/goods-system-migration/tasks.md @@ -6,29 +6,29 @@ ## Tasks -- [ ] 1. 基础设施准备 - - [ ] 1.1 创建商品相关的DTO和Request/Response模型 +- [x] 1. 基础设施准备 + - [x] 1.1 创建商品相关的DTO和Request/Response模型 - 在HoneyBox.Model/Models目录下创建Goods相关模型 - 包括GoodsListDto、GoodsDetailResponse、BoxDetailDto、CollectionDto、PrizeLogsResponse等 - _Requirements: 1.1-10.3_ - - [ ] 1.2 创建服务接口定义 + - [x] 1.2 创建服务接口定义 - 在HoneyBox.Core/Interfaces目录下创建IGoodsService、ICollectionService、IPrizeService、IGoodsCacheService接口 - _Requirements: 1.1-10.3_ - - [ ] 1.3 创建数据库实体模型 + - [x] 1.3 创建数据库实体模型 - 创建Goods、GoodsList、GoodsType、GoodsExtend、GoodsCollection等实体 - 配置EF Core映射 - _Requirements: 1.1-10.3_ - - [ ] 1.4 注册服务到DI容器 + - [x] 1.4 注册服务到DI容器 - 在ServiceModule.cs中注册新服务 - _Requirements: 1.1-10.3_ -- [ ] 2. 商品列表服务实现 - - [ ] 2.1 查看PHP代码了解商品列表业务逻辑 +- [x] 2. 商品列表服务实现 + - [x] 2.1 查看PHP代码了解商品列表业务逻辑 - 阅读server/php/app/api/controller/Goods.php中的goods_list方法 - 理解type过滤、unlock_amount过滤、分页、排序逻辑 - 理解join_count统计逻辑 - _Requirements: 1.1-1.6_ - - [ ] 2.2 实现GoodsService - 商品列表 + - [x] 2.2 实现GoodsService - 商品列表 - 实现GetGoodsListAsync方法 - 实现type过滤逻辑 - 实现unlock_amount过滤逻辑 @@ -39,12 +39,12 @@ - **Property 1: 商品列表过滤和排序正确性** - **Validates: Requirements 1.1, 1.4, 1.5, 1.6** -- [ ] 3. 商品详情服务实现 - - [ ] 3.1 查看PHP代码了解商品详情业务逻辑 +- [x] 3. 商品详情服务实现 + - [x] 3.1 查看PHP代码了解商品详情业务逻辑 - 阅读server/php/app/api/controller/Goods.php中的goods_detail方法 - 理解奖品列表查询、概率计算、锁箱信息、参与用户统计 - _Requirements: 2.1-2.7_ - - [ ] 3.2 实现GoodsService - 商品详情 + - [x] 3.2 实现GoodsService - 商品详情 - 实现GetGoodsDetailAsync方法 - 实现自动选择箱号逻辑(goods_num=0时) - 实现奖品列表查询 @@ -59,12 +59,12 @@ - **Property 3: 奖品概率计算正确性** - **Validates: Requirements 2.1, 2.3, 2.4, 2.5, 2.6, 2.7** -- [ ] 4. 商品子奖品服务实现 - - [ ] 4.1 查看PHP代码了解子奖品业务逻辑 +- [x] 4. 商品子奖品服务实现 + - [x] 4.1 查看PHP代码了解子奖品业务逻辑 - 阅读server/php/app/api/controller/Goods.php中的goods_children方法 - 理解子奖品查询和real_pro计算 - _Requirements: 3.1-3.3_ - - [ ] 4.2 实现GoodsService - 子奖品查询 + - [x] 4.2 实现GoodsService - 子奖品查询 - 实现GetGoodsChildrenAsync方法 - 实现real_pro计算逻辑 - 实现shang_info关联查询 @@ -73,30 +73,30 @@ - **Property 4: 子奖品数据完整性** - **Validates: Requirements 3.1, 3.2, 3.3** -- [ ] 5. 商品扩展配置服务实现 - - [ ] 5.1 查看PHP代码了解扩展配置业务逻辑 +- [x] 5. 商品扩展配置服务实现 + - [x] 5.1 查看PHP代码了解扩展配置业务逻辑 - 阅读server/php/app/api/controller/Goods.php中的goods_extend方法 - 理解配置优先级(商品配置 > 类型默认配置) - _Requirements: 4.1-4.3_ - - [ ] 5.2 实现GoodsService - 扩展配置 + - [x] 5.2 实现GoodsService - 扩展配置 - 实现GetGoodsExtendAsync方法 - 实现配置优先级逻辑 - _Requirements: 4.1-4.3_ -- [ ] 6. Checkpoint - 核心商品服务测试验证 +- [x] 6. Checkpoint - 核心商品服务测试验证 - 确保商品列表、详情、子奖品、扩展配置服务测试通过 - 如有问题请询问用户 -- [ ] 7. 箱号管理服务实现 - - [ ] 7.1 查看PHP代码了解箱号管理业务逻辑 +- [x] 7. 箱号管理服务实现 + - [x] 7.1 查看PHP代码了解箱号管理业务逻辑 - 阅读server/php/app/api/controller/Goods.php中的goods_num_list、goods_num_detail方法 - 理解箱号分组、排序、余量统计逻辑 - _Requirements: 5.1-5.4_ - - [ ] 7.2 实现GoodsService - 箱号列表 + - [x] 7.2 实现GoodsService - 箱号列表 - 实现GetBoxListAsync方法 - 实现箱号分组逻辑(每10个一组) - _Requirements: 5.1_ - - [ ] 7.3 实现GoodsService - 箱号详情 + - [x] 7.3 实现GoodsService - 箱号详情 - 实现GetBoxDetailAsync方法 - 实现排序逻辑(箱号升序/降序、余量降序) - 实现余量统计 @@ -106,12 +106,12 @@ - **Property 5: 箱号管理数据正确性** - **Validates: Requirements 5.1, 5.2, 5.3, 5.4** -- [ ] 8. 收藏服务实现 - - [ ] 8.1 查看PHP代码了解收藏业务逻辑 +- [x] 8. 收藏服务实现 + - [x] 8.1 查看PHP代码了解收藏业务逻辑 - 阅读server/php/app/api/controller/Goods.php中的collection、collection_list方法 - 理解收藏/取消收藏逻辑 - _Requirements: 6.1-6.4_ - - [ ] 8.2 实现CollectionService + - [x] 8.2 实现CollectionService - 实现ToggleCollectionAsync方法(收藏/取消收藏) - 实现GetCollectionListAsync方法(收藏列表) - 实现IsCollectedAsync方法(检查收藏状态) @@ -121,22 +121,22 @@ - **Property 6: 收藏操作往返一致性** - **Validates: Requirements 6.1, 6.2, 6.4** -- [ ] 9. 奖品统计服务实现 - - [ ] 9.1 查看PHP代码了解奖品统计业务逻辑 +- [x] 9. 奖品统计服务实现 + - [x] 9.1 查看PHP代码了解奖品统计业务逻辑 - 阅读server/php/app/api/controller/Goods.php中的goods_prize_count、goods_prize_content方法 - 理解统计计算逻辑 - _Requirements: 7.1-7.2_ - - [ ] 9.2 实现PrizeService - 奖品统计 + - [x] 9.2 实现PrizeService - 奖品统计 - 实现GetPrizeCountAsync方法 - 实现GetPrizeContentAsync方法 - _Requirements: 7.1-7.2_ -- [ ] 10. 中奖记录服务实现 - - [ ] 10.1 查看PHP代码了解中奖记录业务逻辑 +- [x] 10. 中奖记录服务实现 + - [x] 10.1 查看PHP代码了解中奖记录业务逻辑 - 阅读server/php/app/api/controller/Goods.php中的goods_prize_logs方法 - 理解分类筛选、分页逻辑 - _Requirements: 8.1-8.4_ - - [ ] 10.2 实现PrizeService - 中奖记录 + - [x] 10.2 实现PrizeService - 中奖记录 - 实现GetPrizeLogsAsync方法 - 实现shang_id筛选 - 实现分类列表查询 @@ -146,74 +146,74 @@ - **Property 7: 中奖记录数据完整性** - **Validates: Requirements 8.1, 8.2, 8.3, 8.4** -- [ ] 11. 缓存服务实现 - - [ ] 11.1 实现GoodsCacheService +- [x] 11. 缓存服务实现 + - [x] 11.1 实现GoodsCacheService - 实现GetJoinCountAsync方法 - 实现IncrementJoinCountAsync方法 - 实现InvalidateGoodsCacheAsync方法 - 配置缓存过期时间 - _Requirements: 9.1-9.4_ -- [ ] 12. Checkpoint - 服务层测试验证 +- [x] 12. Checkpoint - 服务层测试验证 - 确保所有服务层单元测试通过 - 确保所有属性测试通过 - 如有问题请询问用户 -- [ ] 13. 控制器实现 - GoodsController - - [ ] 13.1 实现商品列表接口 POST /goods_list +- [x] 13. 控制器实现 - GoodsController + - [x] 13.1 实现商品列表接口 POST /goods_list - 调用GoodsService.GetGoodsListAsync - 更新API接口文档标记迁移状态 - _Requirements: 1.1-1.6_ - - [ ] 13.2 实现商品详情接口 POST /goods_detail + - [x] 13.2 实现商品详情接口 POST /goods_detail - 调用GoodsService.GetGoodsDetailAsync - 更新API接口文档标记迁移状态 - _Requirements: 2.1-2.7_ - - [ ] 13.3 实现商品子奖品接口 POST /goods_children + - [x] 13.3 实现商品子奖品接口 POST /goods_children - 调用GoodsService.GetGoodsChildrenAsync - 更新API接口文档标记迁移状态 - _Requirements: 3.1-3.3_ - - [ ] 13.4 实现商品扩展配置接口 POST /goods_extend + - [x] 13.4 实现商品扩展配置接口 POST /goods_extend - 调用GoodsService.GetGoodsExtendAsync - 更新API接口文档标记迁移状态 - _Requirements: 4.1-4.3_ - - [ ] 13.5 实现箱号列表接口 POST /goods_num_list + - [x] 13.5 实现箱号列表接口 POST /goods_num_list - 调用GoodsService.GetBoxListAsync - 更新API接口文档标记迁移状态 - _Requirements: 5.1_ - - [ ] 13.6 实现箱号详情接口 POST /goods_num_detail + - [x] 13.6 实现箱号详情接口 POST /goods_num_detail - 调用GoodsService.GetBoxDetailAsync - 更新API接口文档标记迁移状态 - _Requirements: 5.2-5.4_ - - [ ] 13.7 实现奖品数量统计接口 POST /goods_prize_count + - [x] 13.7 实现奖品数量统计接口 POST /goods_prize_count - 调用PrizeService.GetPrizeCountAsync - 更新API接口文档标记迁移状态 - _Requirements: 7.1_ - - [ ] 13.8 实现奖品内容接口 POST /goods_prize_content + - [x] 13.8 实现奖品内容接口 POST /goods_prize_content - 调用PrizeService.GetPrizeContentAsync - 更新API接口文档标记迁移状态 - _Requirements: 7.2_ - - [ ] 13.9 实现中奖记录接口 POST /goods_prize_logs + - [x] 13.9 实现中奖记录接口 POST /goods_prize_logs - 调用PrizeService.GetPrizeLogsAsync - 更新API接口文档标记迁移状态 - _Requirements: 8.1-8.4_ -- [ ] 14. 控制器实现 - CollectionController - - [ ] 14.1 实现收藏/取消收藏接口 POST /collection +- [x] 14. 控制器实现 - CollectionController + - [x] 14.1 实现收藏/取消收藏接口 POST /collection - 调用CollectionService.ToggleCollectionAsync - 更新API接口文档标记迁移状态 - _Requirements: 6.1-6.3_ - - [ ] 14.2 实现收藏列表接口 POST /collection_list + - [x] 14.2 实现收藏列表接口 POST /collection_list - 调用CollectionService.GetCollectionListAsync - 更新API接口文档标记迁移状态 - _Requirements: 6.4_ -- [ ] 15. Checkpoint - 控制器测试验证 +- [x] 15. Checkpoint - 控制器测试验证 - 确保所有控制器接口可正常访问 - 使用Postman或HTTP文件测试各接口 - 如有问题请询问用户 -- [ ] 16. API响应格式验证 - - [ ] 16.1 验证所有接口响应格式 +- [x] 16. API响应格式验证 + - [x] 16.1 验证所有接口响应格式 - 确保status、msg、data结构一致 - 确保字段命名与PHP API一致(snake_case) - _Requirements: 10.1, 10.3_ @@ -221,29 +221,29 @@ - **Property 8: API响应格式一致性** - **Validates: Requirements 10.1, 10.3** -- [ ] 17. 集成测试 - - [ ] 17.1 编写商品列表集成测试 +- [x] 17. 集成测试 + - [x] 17.1 编写商品列表集成测试 - 测试完整的商品查询流程 - 测试分类筛选 - _Requirements: 1.1-1.6_ - - [ ] 17.2 编写商品详情集成测试 + - [x] 17.2 编写商品详情集成测试 - 测试商品详情查询 - 测试概率计算 - _Requirements: 2.1-2.7_ - - [ ] 17.3 编写收藏功能集成测试 + - [x] 17.3 编写收藏功能集成测试 - 测试收藏/取消收藏流程 - _Requirements: 6.1-6.4_ -- [ ] 18. 文档更新和最终验证 - - [ ] 18.1 更新API接口文档 +- [x] 18. 文档更新和最终验证 + - [x] 18.1 更新API接口文档 - 确认所有迁移接口都已标记 - 记录新接口地址 - _Requirements: 10.4_ - - [ ] 18.2 创建HTTP测试文件 + - [x] 18.2 创建HTTP测试文件 - 在HoneyBox.Api目录下创建goods-system.http测试文件 - 包含所有商品系统相关接口的测试请求 -- [ ] 19. Final Checkpoint - 完整功能验证 +- [x] 19. Final Checkpoint - 完整功能验证 - 确保所有测试通过 - 确保API文档已更新 - 确保与前端兼容性 diff --git a/docs/API接口文档.md b/docs/API接口文档.md index 9ff40b5c..4a1f6e34 100644 --- a/docs/API接口文档.md +++ b/docs/API接口文档.md @@ -371,6 +371,9 @@ Authorization: Bearer {token} POST /goods ``` +**迁移状态**: ✅ 已迁移到 .NET 8 +**新接口地址**: `POST /api/goods_list` + **请求参数:** ```json { @@ -419,6 +422,9 @@ POST /goods POST /goodsdetail ``` +**迁移状态**: ✅ 已迁移到 .NET 8 +**新接口地址**: `POST /api/goods_detail` + **请求参数:** ```json { @@ -460,7 +466,178 @@ POST /goodsdetail } ``` -### 3.3 获取无限赏商品详情 +### 3.3 获取商品子奖品 +```http +POST /goods_children +``` + +**迁移状态**: ✅ 已迁移到 .NET 8 +**新接口地址**: `POST /api/goods_children` + +**请求参数:** +```json +{ + "goods_id": 1001, + "goods_num": 0, + "goods_list_id": 2001 +} +``` + +**参数说明:** +| 参数 | 类型 | 必填 | 说明 | +|------|------|------|------| +| goods_id | int | 是 | 商品ID | +| goods_num | int | 否 | 箱号,默认0 | +| goods_list_id | int | 是 | 奖品ID | + +**响应示例:** +```json +{ + "status": 1, + "msg": "success", + "data": [ + { + "id": 3001, + "title": "子奖品名称", + "imgurl": "子奖品图片", + "price": "99.00", + "real_pro": "0.05000", + "shang_info": { + "id": 1, + "title": "A赏", + "color": "#FF0000" + } + } + ] +} +``` + +### 3.4 获取商品扩展配置 +```http +POST /goods_extend +``` + +**迁移状态**: ✅ 已迁移到 .NET 8 +**新接口地址**: `POST /api/goods_extend` + +**请求参数:** +```json +{ + "goods_id": 1001, + "goods_type": 1 +} +``` + +**参数说明:** +| 参数 | 类型 | 必填 | 说明 | +|------|------|------|------| +| goods_id | int | 是 | 商品ID | +| goods_type | int | 否 | 商品类型 | + +**响应示例:** +```json +{ + "status": 1, + "msg": "success", + "data": { + "pay_wechat": 1, + "pay_balance": 1, + "pay_currency": 1, + "pay_currency2": 1, + "dk_money": 1, + "dk_integral": 1, + "dk_money2": 1 + } +} +``` + +### 3.5 获取箱号列表 +```http +POST /goods_num_list +``` + +**迁移状态**: ✅ 已迁移到 .NET 8 +**新接口地址**: `POST /api/goods_num_list` + +**请求参数:** +```json +{ + "goods_id": 1001 +} +``` + +**参数说明:** +| 参数 | 类型 | 必填 | 说明 | +|------|------|------|------| +| goods_id | int | 是 | 商品ID | + +**响应示例:** +```json +{ + "status": 1, + "msg": "success", + "data": [ + { + "start": 1, + "end": 10, + "text": "1-10" + }, + { + "start": 11, + "end": 20, + "text": "11-20" + } + ] +} +``` + +### 3.6 获取箱号详情 +```http +POST /goods_num_detail +``` + +**迁移状态**: ✅ 已迁移到 .NET 8 +**新接口地址**: `POST /api/goods_num_detail` + +**请求参数:** +```json +{ + "goods_id": 1001, + "page_no": 0, + "sort": 0 +} +``` + +**参数说明:** +| 参数 | 类型 | 必填 | 说明 | +|------|------|------|------| +| goods_id | int | 是 | 商品ID | +| page_no | int | 否 | 页码,默认0(第一组) | +| sort | int | 否 | 排序:0-箱号升序,1-箱号降序,2-余量降序 | + +**响应示例:** +```json +{ + "status": 1, + "msg": "success", + "data": [ + { + "num": 1, + "surplus_all_stock": 50, + "goods_list": [ + { + "id": 2001, + "title": "奖品名称", + "imgurl": "奖品图片", + "surplus_stock": 5 + } + ] + } + ] +} +``` + +### 3.7 获取无限赏商品详情 ```http POST /infinite_goodsdetail ``` @@ -505,6 +682,9 @@ POST /infinite_goodsdetail POST /goodslist_count ``` +**迁移状态**: ✅ 已迁移到 .NET 8 +**新接口地址**: `POST /api/goods_prize_count` + **请求参数:** ```json { @@ -517,6 +697,9 @@ POST /goodslist_count POST /goodslist_content ``` +**迁移状态**: ✅ 已迁移到 .NET 8 +**新接口地址**: `POST /api/goods_prize_content` + **请求参数:** ```json { @@ -786,29 +969,58 @@ POST /infinite_prizeorderlog POST /shang_log ``` +**迁移状态**: ✅ 已迁移到 .NET 8 +**新接口地址**: `POST /api/goods_prize_logs` + **请求参数:** ```json { "goods_id": 1001, - "num": 0, - "page": 1 + "goods_num": 0, + "shang_id": 0, + "page": 1, + "page_size": 15 } ``` +**参数说明:** +| 参数 | 类型 | 必填 | 说明 | +|------|------|------|------| +| goods_id | int | 是 | 商品ID | +| goods_num | int | 否 | 箱号,默认0 | +| shang_id | int | 否 | 赏品分类ID,0=全部 | +| page | int | 否 | 页码,默认1 | +| page_size | int | 否 | 每页数量,默认15 | + **响应示例:** ```json { "status": 1, "msg": "success", "data": { + "category": [ + { + "id": 1, + "title": "A赏", + "color": "#FF0000" + } + ], "data": [ { "user_nickname": "用户***", + "user_headimg": "头像URL", "goodslist_title": "限定手办A", + "goodslist_imgurl": "奖品图片", + "shang_info": { + "id": 1, + "title": "A赏", + "color": "#FF0000" + }, "addtime": "2024-01-01 10:30:00", "luck_no": 1 } - ] + ], + "last_page": 10 } } ``` @@ -1958,12 +2170,104 @@ POST /receive POST /addCollect ``` +**迁移状态**: ✅ 已迁移到 .NET 8 +**新接口地址**: `POST /api/addCollect` + **请求参数:** ```json { "goods_id": 1001, - "type": 1, - "num": 0 + "goods_num": 0 +} +``` + +**参数说明:** +| 参数 | 类型 | 必填 | 说明 | +|------|------|------|------| +| goods_id | int | 是 | 商品ID | +| goods_num | int | 否 | 箱号,默认0 | + +**响应示例:** +```json +{ + "status": 1, + "msg": "操作成功" +} +``` + +### 9.7 收藏列表 +```http +POST /listCollect +``` + +**迁移状态**: ✅ 已迁移到 .NET 8 +**新接口地址**: `POST /api/listCollect` + +**请求参数:** +```json +{ + "type": 0, + "page": 1, + "page_size": 100 +} +``` + +**参数说明:** +| 参数 | 类型 | 必填 | 说明 | +|------|------|------|------| +| type | int | 否 | 商品类型,0=全部 | +| page | int | 否 | 页码,默认1 | +| page_size | int | 否 | 每页数量,默认100 | + +**响应示例:** +```json +{ + "status": 1, + "msg": "success", + "data": { + "data": [ + { + "id": 1, + "goods_id": 1001, + "type": 1, + "num": 0, + "goods_title": "商品标题", + "goods_price": "99.00", + "imgurl": "图片URL", + "stock": 100, + "surplus_stock": 50 + } + ], + "last_page": 1 + } +} +``` + +### 9.8 删除收藏 +```http +POST /delCollect +``` + +**迁移状态**: ✅ 已迁移到 .NET 8 +**新接口地址**: `POST /api/delCollect` + +**请求参数:** +```json +{ + "id": 1 +} +``` + +**参数说明:** +| 参数 | 类型 | 必填 | 说明 | +|------|------|------|------| +| id | int | 是 | 收藏记录ID | + +**响应示例:** +```json +{ + "status": 1, + "msg": "删除成功" } ``` diff --git a/server/C#/HoneyBox/src/HoneyBox.Api/Controllers/CollectionController.cs b/server/C#/HoneyBox/src/HoneyBox.Api/Controllers/CollectionController.cs new file mode 100644 index 00000000..5ad6bd89 --- /dev/null +++ b/server/C#/HoneyBox/src/HoneyBox.Api/Controllers/CollectionController.cs @@ -0,0 +1,170 @@ +using System.Security.Claims; +using HoneyBox.Core.Interfaces; +using HoneyBox.Model.Base; +using HoneyBox.Model.Models.Goods; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace HoneyBox.Api.Controllers; + +/// +/// 收藏控制器 +/// Requirements: 6.1-6.4 +/// +[ApiController] +[Route("api")] +public class CollectionController : ControllerBase +{ + private readonly ICollectionService _collectionService; + private readonly ILogger _logger; + + public CollectionController( + ICollectionService collectionService, + ILogger logger) + { + _collectionService = collectionService; + _logger = logger; + } + + /// + /// 收藏/取消收藏商品 + /// POST /api/addCollect + /// Requirements: 6.1-6.3 + /// + /// + /// 切换收藏状态:如果已收藏则取消收藏,如果未收藏则添加收藏 + /// + [HttpPost("addCollect")] + [Authorize] + public async Task ToggleCollection([FromBody] CollectionRequest? request) + { + var userId = GetCurrentUserId(); + if (userId == null) + { + return ApiResponse.Unauthorized(); + } + + try + { + if (request == null || request.GoodsId <= 0) + { + return ApiResponse.Fail("商品ID不能为空"); + } + + var result = await _collectionService.ToggleCollectionAsync( + userId.Value, + request.GoodsId, + request.GoodsNum); + + return result + ? ApiResponse.Success("操作成功") + : ApiResponse.Fail("操作失败"); + } + catch (InvalidOperationException ex) + { + _logger.LogWarning("Toggle collection failed: UserId={UserId}, GoodsId={GoodsId}, Error={Error}", + userId, request?.GoodsId, ex.Message); + return ApiResponse.Fail(ex.Message); + } + catch (Exception ex) + { + _logger.LogError(ex, "Failed to toggle collection: UserId={UserId}, GoodsId={GoodsId}", + userId, request?.GoodsId); + return ApiResponse.Fail("操作失败"); + } + } + + /// + /// 获取收藏列表 + /// POST /api/listCollect + /// Requirements: 6.4 + /// + [HttpPost("listCollect")] + [Authorize] + public async Task> GetCollectionList([FromBody] CollectionListRequest? request) + { + var userId = GetCurrentUserId(); + if (userId == null) + { + return ApiResponse.Unauthorized(); + } + + try + { + request ??= new CollectionListRequest(); + var page = request.Page < 1 ? 1 : request.Page; + var pageSize = request.PageSize < 1 ? 100 : request.PageSize; + + var result = await _collectionService.GetCollectionListAsync( + userId.Value, + request.Type, + page, + pageSize); + + return ApiResponse.Success(result); + } + catch (Exception ex) + { + _logger.LogError(ex, "Failed to get collection list: UserId={UserId}", userId); + return ApiResponse.Fail("获取收藏列表失败"); + } + } + + /// + /// 删除收藏 + /// POST /api/delCollect + /// + [HttpPost("delCollect")] + [Authorize] + public async Task DeleteCollection([FromBody] DeleteCollectionRequest? request) + { + var userId = GetCurrentUserId(); + if (userId == null) + { + return ApiResponse.Unauthorized(); + } + + try + { + if (request == null || request.Id <= 0) + { + return ApiResponse.Fail("收藏ID不能为空"); + } + + var result = await _collectionService.DeleteCollectionAsync(userId.Value, request.Id); + + return result + ? ApiResponse.Success("删除成功") + : ApiResponse.Fail("删除失败"); + } + catch (InvalidOperationException ex) + { + _logger.LogWarning("Delete collection failed: UserId={UserId}, CollectionId={CollectionId}, Error={Error}", + userId, request?.Id, ex.Message); + return ApiResponse.Fail(ex.Message); + } + catch (Exception ex) + { + _logger.LogError(ex, "Failed to delete collection: UserId={UserId}, CollectionId={CollectionId}", + userId, request?.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; + } + + #endregion +} diff --git a/server/C#/HoneyBox/src/HoneyBox.Api/Controllers/GoodsController.cs b/server/C#/HoneyBox/src/HoneyBox.Api/Controllers/GoodsController.cs new file mode 100644 index 00000000..889862b5 --- /dev/null +++ b/server/C#/HoneyBox/src/HoneyBox.Api/Controllers/GoodsController.cs @@ -0,0 +1,349 @@ +using System.Security.Claims; +using HoneyBox.Core.Interfaces; +using HoneyBox.Model.Base; +using HoneyBox.Model.Models; +using HoneyBox.Model.Models.Goods; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace HoneyBox.Api.Controllers; + +/// +/// 商品控制器 +/// +[ApiController] +[Route("api")] +public class GoodsController : ControllerBase +{ + private readonly IGoodsService _goodsService; + private readonly IPrizeService _prizeService; + private readonly ILogger _logger; + + public GoodsController( + IGoodsService goodsService, + IPrizeService prizeService, + ILogger logger) + { + _goodsService = goodsService; + _prizeService = prizeService; + _logger = logger; + } + + /// + /// 获取商品列表 + /// POST /api/goods_list + /// Requirements: 1.1-1.6 + /// + [HttpPost("goods_list")] + [Authorize] + public async Task>> GetGoodsList([FromBody] GoodsListRequest? request) + { + var userId = GetCurrentUserId(); + if (userId == null) + { + return ApiResponse>.Unauthorized(); + } + + try + { + request ??= new GoodsListRequest(); + if (request.Page < 1) request.Page = 1; + if (request.PageSize < 1) request.PageSize = 15; + + var result = await _goodsService.GetGoodsListAsync(request, userId.Value); + return ApiResponse>.Success(result); + } + catch (Exception ex) + { + _logger.LogError(ex, "Failed to get goods list: UserId={UserId}", userId); + return ApiResponse>.Fail("获取商品列表失败"); + } + } + + /// + /// 获取商品详情 + /// POST /api/goods_detail + /// Requirements: 2.1-2.7 + /// + [HttpPost("goods_detail")] + [Authorize] + public async Task> GetGoodsDetail([FromBody] GoodsDetailRequest? request) + { + var userId = GetCurrentUserId(); + if (userId == null) + { + return ApiResponse.Unauthorized(); + } + + try + { + if (request == null || request.GoodsId <= 0) + { + return ApiResponse.Fail("商品ID不能为空"); + } + + var result = await _goodsService.GetGoodsDetailAsync(request.GoodsId, request.GoodsNum, userId.Value); + return ApiResponse.Success(result); + } + catch (InvalidOperationException ex) + { + _logger.LogWarning("Get goods detail failed: GoodsId={GoodsId}, Error={Error}", request?.GoodsId, ex.Message); + return ApiResponse.Fail(ex.Message); + } + catch (Exception ex) + { + _logger.LogError(ex, "Failed to get goods detail: GoodsId={GoodsId}", request?.GoodsId); + return ApiResponse.Fail("获取商品详情失败"); + } + } + + /// + /// 获取商品子奖品列表 + /// POST /api/goods_children + /// Requirements: 3.1-3.3 + /// + [HttpPost("goods_children")] + [Authorize] + public async Task>> GetGoodsChildren([FromBody] GoodsChildrenRequest? request) + { + var userId = GetCurrentUserId(); + if (userId == null) + { + return ApiResponse>.Unauthorized(); + } + + try + { + if (request == null || request.GoodsId <= 0 || request.GoodsListId <= 0) + { + return ApiResponse>.Fail("参数不能为空"); + } + + var result = await _goodsService.GetGoodsChildrenAsync(request.GoodsId, request.GoodsNum, request.GoodsListId); + return ApiResponse>.Success(result); + } + catch (Exception ex) + { + _logger.LogError(ex, "Failed to get goods children: GoodsId={GoodsId}, GoodsListId={GoodsListId}", + request?.GoodsId, request?.GoodsListId); + return ApiResponse>.Fail("获取子奖品列表失败"); + } + } + + /// + /// 获取商品扩展配置 + /// POST /api/goods_extend + /// Requirements: 4.1-4.3 + /// + [HttpPost("goods_extend")] + [Authorize] + public async Task> GetGoodsExtend([FromBody] GoodsExtendRequest? request) + { + var userId = GetCurrentUserId(); + if (userId == null) + { + return ApiResponse.Unauthorized(); + } + + try + { + if (request == null || request.GoodsId <= 0) + { + return ApiResponse.Fail("商品ID不能为空"); + } + + var result = await _goodsService.GetGoodsExtendAsync(request.GoodsId, request.GoodsType); + return ApiResponse.Success(result); + } + catch (Exception ex) + { + _logger.LogError(ex, "Failed to get goods extend: GoodsId={GoodsId}", request?.GoodsId); + return ApiResponse.Fail("获取商品扩展配置失败"); + } + } + + /// + /// 获取箱号列表 + /// POST /api/goods_num_list + /// Requirements: 5.1 + /// + [HttpPost("goods_num_list")] + [Authorize] + public async Task>> GetBoxList([FromBody] BoxListRequest? request) + { + var userId = GetCurrentUserId(); + if (userId == null) + { + return ApiResponse>.Unauthorized(); + } + + try + { + if (request == null || request.GoodsId <= 0) + { + return ApiResponse>.Fail("商品ID不能为空"); + } + + var result = await _goodsService.GetBoxListAsync(request.GoodsId); + return ApiResponse>.Success(result); + } + catch (Exception ex) + { + _logger.LogError(ex, "Failed to get box list: GoodsId={GoodsId}", request?.GoodsId); + return ApiResponse>.Fail("获取箱号列表失败"); + } + } + + /// + /// 获取箱号详情 + /// POST /api/goods_num_detail + /// Requirements: 5.2-5.4 + /// + [HttpPost("goods_num_detail")] + [Authorize] + public async Task>> GetBoxDetail([FromBody] BoxDetailRequest? request) + { + var userId = GetCurrentUserId(); + if (userId == null) + { + return ApiResponse>.Unauthorized(); + } + + try + { + if (request == null || request.GoodsId <= 0) + { + return ApiResponse>.Fail("商品ID不能为空"); + } + + var result = await _goodsService.GetBoxDetailAsync(request.GoodsId, request.PageNo, request.Sort); + return ApiResponse>.Success(result); + } + catch (Exception ex) + { + _logger.LogError(ex, "Failed to get box detail: GoodsId={GoodsId}", request?.GoodsId); + return ApiResponse>.Fail("获取箱号详情失败"); + } + } + + /// + /// 获取奖品数量统计 + /// POST /api/goods_prize_count + /// Requirements: 7.1 + /// + [HttpPost("goods_prize_count")] + [Authorize] + public async Task> GetPrizeCount([FromBody] PrizeCountRequest? request) + { + var userId = GetCurrentUserId(); + if (userId == null) + { + return ApiResponse.Unauthorized(); + } + + try + { + if (request == null || request.GoodsId <= 0) + { + return ApiResponse.Fail("商品ID不能为空"); + } + + var result = await _prizeService.GetPrizeCountAsync(request.GoodsId); + return ApiResponse.Success(result); + } + catch (Exception ex) + { + _logger.LogError(ex, "Failed to get prize count: GoodsId={GoodsId}", request?.GoodsId); + return ApiResponse.Fail("获取奖品统计失败"); + } + } + + /// + /// 获取奖品内容 + /// POST /api/goods_prize_content + /// Requirements: 7.2 + /// + [HttpPost("goods_prize_content")] + [Authorize] + public async Task> GetPrizeContent([FromBody] PrizeContentRequest? request) + { + var userId = GetCurrentUserId(); + if (userId == null) + { + return ApiResponse.Unauthorized(); + } + + try + { + if (request == null || request.GoodsId <= 0) + { + return ApiResponse.Fail("商品ID不能为空"); + } + + var result = await _prizeService.GetPrizeContentAsync(request.GoodsId, request.Num); + return ApiResponse.Success(result); + } + catch (Exception ex) + { + _logger.LogError(ex, "Failed to get prize content: GoodsId={GoodsId}", request?.GoodsId); + return ApiResponse.Fail("获取奖品内容失败"); + } + } + + /// + /// 获取中奖记录 + /// POST /api/goods_prize_logs + /// Requirements: 8.1-8.4 + /// + [HttpPost("goods_prize_logs")] + [Authorize] + public async Task> GetPrizeLogs([FromBody] PrizeLogsRequest? request) + { + var userId = GetCurrentUserId(); + if (userId == null) + { + return ApiResponse.Unauthorized(); + } + + try + { + if (request == null || request.GoodsId <= 0) + { + return ApiResponse.Fail("商品ID不能为空"); + } + + var page = request.Page < 1 ? 1 : request.Page; + var pageSize = request.PageSize < 1 ? 15 : request.PageSize; + + var result = await _prizeService.GetPrizeLogsAsync( + request.GoodsId, + request.GoodsNum, + request.ShangId, + page, + pageSize); + return ApiResponse.Success(result); + } + catch (Exception ex) + { + _logger.LogError(ex, "Failed to get prize logs: GoodsId={GoodsId}", request?.GoodsId); + 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; + } + + #endregion +} diff --git a/server/C#/HoneyBox/src/HoneyBox.Api/bin/Debug/net10.0/HoneyBox.Api.dll b/server/C#/HoneyBox/src/HoneyBox.Api/bin/Debug/net10.0/HoneyBox.Api.dll index 7fd04533..9e781166 100644 Binary files a/server/C#/HoneyBox/src/HoneyBox.Api/bin/Debug/net10.0/HoneyBox.Api.dll and b/server/C#/HoneyBox/src/HoneyBox.Api/bin/Debug/net10.0/HoneyBox.Api.dll differ diff --git a/server/C#/HoneyBox/src/HoneyBox.Api/bin/Debug/net10.0/HoneyBox.Api.exe b/server/C#/HoneyBox/src/HoneyBox.Api/bin/Debug/net10.0/HoneyBox.Api.exe index f88859cd..23d27f01 100644 Binary files a/server/C#/HoneyBox/src/HoneyBox.Api/bin/Debug/net10.0/HoneyBox.Api.exe and b/server/C#/HoneyBox/src/HoneyBox.Api/bin/Debug/net10.0/HoneyBox.Api.exe differ diff --git a/server/C#/HoneyBox/src/HoneyBox.Api/bin/Debug/net10.0/HoneyBox.Api.pdb b/server/C#/HoneyBox/src/HoneyBox.Api/bin/Debug/net10.0/HoneyBox.Api.pdb index 258e7863..02651cb2 100644 Binary files a/server/C#/HoneyBox/src/HoneyBox.Api/bin/Debug/net10.0/HoneyBox.Api.pdb and b/server/C#/HoneyBox/src/HoneyBox.Api/bin/Debug/net10.0/HoneyBox.Api.pdb differ diff --git a/server/C#/HoneyBox/src/HoneyBox.Api/bin/Debug/net10.0/HoneyBox.Core.dll b/server/C#/HoneyBox/src/HoneyBox.Api/bin/Debug/net10.0/HoneyBox.Core.dll index 82fbad46..eb1d495c 100644 Binary files a/server/C#/HoneyBox/src/HoneyBox.Api/bin/Debug/net10.0/HoneyBox.Core.dll and b/server/C#/HoneyBox/src/HoneyBox.Api/bin/Debug/net10.0/HoneyBox.Core.dll differ diff --git a/server/C#/HoneyBox/src/HoneyBox.Api/bin/Debug/net10.0/HoneyBox.Core.pdb b/server/C#/HoneyBox/src/HoneyBox.Api/bin/Debug/net10.0/HoneyBox.Core.pdb index f37d0d47..5ea93cf6 100644 Binary files a/server/C#/HoneyBox/src/HoneyBox.Api/bin/Debug/net10.0/HoneyBox.Core.pdb and b/server/C#/HoneyBox/src/HoneyBox.Api/bin/Debug/net10.0/HoneyBox.Core.pdb differ diff --git a/server/C#/HoneyBox/src/HoneyBox.Api/bin/Debug/net10.0/HoneyBox.Infrastructure.dll b/server/C#/HoneyBox/src/HoneyBox.Api/bin/Debug/net10.0/HoneyBox.Infrastructure.dll index a7e55fc8..dc9fa648 100644 Binary files a/server/C#/HoneyBox/src/HoneyBox.Api/bin/Debug/net10.0/HoneyBox.Infrastructure.dll and b/server/C#/HoneyBox/src/HoneyBox.Api/bin/Debug/net10.0/HoneyBox.Infrastructure.dll differ diff --git a/server/C#/HoneyBox/src/HoneyBox.Api/bin/Debug/net10.0/HoneyBox.Infrastructure.pdb b/server/C#/HoneyBox/src/HoneyBox.Api/bin/Debug/net10.0/HoneyBox.Infrastructure.pdb index 448f556c..de06c9ca 100644 Binary files a/server/C#/HoneyBox/src/HoneyBox.Api/bin/Debug/net10.0/HoneyBox.Infrastructure.pdb and b/server/C#/HoneyBox/src/HoneyBox.Api/bin/Debug/net10.0/HoneyBox.Infrastructure.pdb differ diff --git a/server/C#/HoneyBox/src/HoneyBox.Api/bin/Debug/net10.0/HoneyBox.Model.dll b/server/C#/HoneyBox/src/HoneyBox.Api/bin/Debug/net10.0/HoneyBox.Model.dll index e8943c95..1b4d5a5e 100644 Binary files a/server/C#/HoneyBox/src/HoneyBox.Api/bin/Debug/net10.0/HoneyBox.Model.dll and b/server/C#/HoneyBox/src/HoneyBox.Api/bin/Debug/net10.0/HoneyBox.Model.dll differ diff --git a/server/C#/HoneyBox/src/HoneyBox.Api/bin/Debug/net10.0/HoneyBox.Model.pdb b/server/C#/HoneyBox/src/HoneyBox.Api/bin/Debug/net10.0/HoneyBox.Model.pdb index eee28ec8..e48e680e 100644 Binary files a/server/C#/HoneyBox/src/HoneyBox.Api/bin/Debug/net10.0/HoneyBox.Model.pdb and b/server/C#/HoneyBox/src/HoneyBox.Api/bin/Debug/net10.0/HoneyBox.Model.pdb differ diff --git a/server/C#/HoneyBox/src/HoneyBox.Api/goods-system.http b/server/C#/HoneyBox/src/HoneyBox.Api/goods-system.http new file mode 100644 index 00000000..9d6bd2d8 --- /dev/null +++ b/server/C#/HoneyBox/src/HoneyBox.Api/goods-system.http @@ -0,0 +1,462 @@ +# HoneyBox API 商品系统接口测试文件 +# 用于验证所有商品系统相关的控制器接口 +# Checkpoint 15: 控制器测试验证 + +@baseUrl = http://localhost:5238/api +@contentType = application/json + +# 测试用Token(需要通过登录接口获取真实Token后替换) +# 下面是一个有效的测试Token(用户ID: 21583),有效期至2026年 +@authToken = eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1lIjoi5b6u5L-h55So5oi3MTMxMCIsImV4cCI6MTc2NzQzMTM1OCwidWlkIjoiMzMyMjY2IiwiaHR0cDovL3NjaGVtYXMueG1sc29hcC5vcmcvd3MvMjAwNS8wNS9pZGVudGl0eS9jbGFpbXMvbmFtZWlkZW50aWZpZXIiOiIyMTU4MyIsImF1ZCI6IkhvbmV5Qm94VXNlcnMiLCJpc3MiOiJIb25leUJveCJ9.700XWIUmzEumNk5tNYRshh7M42A8MG1X4yTHuz9PZbc + +# 测试用商品ID(需要替换为数据库中存在的商品ID) +@testGoodsId = 1 +@testGoodsNum = 1 +@testGoodsListId = 1 +@testShangId = 1 + +### ============================================ +### 1. 健康检查接口 +### ============================================ + +### 1.1 健康检查 - 验证服务是否正常运行 +# GET /api/health +GET {{baseUrl}}/health +Accept: {{contentType}} + +### ============================================ +### 2. 商品列表接口 (GoodsController) +### Requirements: 1.1-1.6 +### ============================================ + +### 2.1 获取商品列表 - 全部类型 +# POST /api/goods_list +# Requirements: 1.1, 1.2 +POST {{baseUrl}}/goods_list +Content-Type: {{contentType}} +Authorization: Bearer {{authToken}} + +{ + "type": -1, + "page": 1, + "pageSize": 15 +} + +### 2.2 获取商品列表 - 按类型过滤(一番赏 type=1) +# POST /api/goods_list +# Requirements: 1.1 +POST {{baseUrl}}/goods_list +Content-Type: {{contentType}} +Authorization: Bearer {{authToken}} + +{ + "type": 1, + "page": 1, + "pageSize": 15 +} + +### 2.3 获取商品列表 - 按类型过滤(无限赏 type=2) +# POST /api/goods_list +# Requirements: 1.1 +POST {{baseUrl}}/goods_list +Content-Type: {{contentType}} +Authorization: Bearer {{authToken}} + +{ + "type": 2, + "page": 1, + "pageSize": 15 +} + +### 2.4 获取商品列表 - 第二页 +# POST /api/goods_list +# Requirements: 1.1 +POST {{baseUrl}}/goods_list +Content-Type: {{contentType}} +Authorization: Bearer {{authToken}} + +{ + "type": -1, + "page": 2, + "pageSize": 15 +} + +### ============================================ +### 3. 商品详情接口 (GoodsController) +### Requirements: 2.1-2.7 +### ============================================ + +### 3.1 获取商品详情 - 自动选择箱号 +# POST /api/goods_detail +# Requirements: 2.1, 2.2 +POST {{baseUrl}}/goods_detail +Content-Type: {{contentType}} +Authorization: Bearer {{authToken}} + +{ + "goods_id": {{testGoodsId}}, + "goods_num": 0 +} + +### 3.2 获取商品详情 - 指定箱号 +# POST /api/goods_detail +# Requirements: 2.1, 2.3-2.7 +POST {{baseUrl}}/goods_detail +Content-Type: {{contentType}} +Authorization: Bearer {{authToken}} + +{ + "goods_id": {{testGoodsId}}, + "goods_num": {{testGoodsNum}} +} + +### ============================================ +### 4. 商品子奖品接口 (GoodsController) +### Requirements: 3.1-3.3 +### ============================================ + +### 4.1 获取商品子奖品列表 +# POST /api/goods_children +# Requirements: 3.1-3.3 +POST {{baseUrl}}/goods_children +Content-Type: {{contentType}} +Authorization: Bearer {{authToken}} + +{ + "goods_id": {{testGoodsId}}, + "goods_num": {{testGoodsNum}}, + "goods_list_id": {{testGoodsListId}} +} + +### ============================================ +### 5. 商品扩展配置接口 (GoodsController) +### Requirements: 4.1-4.3 +### ============================================ + +### 5.1 获取商品扩展配置 - 一番赏类型 +# POST /api/goods_extend +# Requirements: 4.1-4.3 +POST {{baseUrl}}/goods_extend +Content-Type: {{contentType}} +Authorization: Bearer {{authToken}} + +{ + "goods_id": {{testGoodsId}}, + "goods_type": 1 +} + + +### 5.2 获取商品扩展配置 - 无限赏类型 +# POST /api/goods_extend +# Requirements: 4.1-4.3 +POST {{baseUrl}}/goods_extend +Content-Type: {{contentType}} +Authorization: Bearer {{authToken}} + +{ + "goods_id": {{testGoodsId}}, + "goods_type": 2 +} + +### ============================================ +### 6. 箱号管理接口 (GoodsController) +### Requirements: 5.1-5.4 +### ============================================ + +### 6.1 获取箱号列表 +# POST /api/goods_num_list +# Requirements: 5.1 +POST {{baseUrl}}/goods_num_list +Content-Type: {{contentType}} +Authorization: Bearer {{authToken}} + +{ + "goods_id": {{testGoodsId}} +} + +### 6.2 获取箱号详情 - 箱号升序 +# POST /api/goods_num_detail +# Requirements: 5.2-5.4 +POST {{baseUrl}}/goods_num_detail +Content-Type: {{contentType}} +Authorization: Bearer {{authToken}} + +{ + "goods_id": {{testGoodsId}}, + "page_no": 0, + "sort": 0 +} + +### 6.3 获取箱号详情 - 箱号降序 +# POST /api/goods_num_detail +# Requirements: 5.2-5.4 +POST {{baseUrl}}/goods_num_detail +Content-Type: {{contentType}} +Authorization: Bearer {{authToken}} + +{ + "goods_id": {{testGoodsId}}, + "page_no": 0, + "sort": 1 +} + +### 6.4 获取箱号详情 - 余量降序 +# POST /api/goods_num_detail +# Requirements: 5.2-5.4 +POST {{baseUrl}}/goods_num_detail +Content-Type: {{contentType}} +Authorization: Bearer {{authToken}} + +{ + "goods_id": {{testGoodsId}}, + "page_no": 0, + "sort": 2 +} + +### ============================================ +### 7. 奖品统计接口 (GoodsController) +### Requirements: 7.1-7.2 +### ============================================ + +### 7.1 获取奖品数量统计 +# POST /api/goods_prize_count +# Requirements: 7.1 +POST {{baseUrl}}/goods_prize_count +Content-Type: {{contentType}} +Authorization: Bearer {{authToken}} + +{ + "goods_id": {{testGoodsId}} +} + +### 7.2 获取奖品内容 - 指定箱号 +# POST /api/goods_prize_content +# Requirements: 7.2 +POST {{baseUrl}}/goods_prize_content +Content-Type: {{contentType}} +Authorization: Bearer {{authToken}} + +{ + "goods_id": {{testGoodsId}}, + "num": {{testGoodsNum}} +} + +### 7.3 获取奖品内容 - 默认箱号 +# POST /api/goods_prize_content +# Requirements: 7.2 +POST {{baseUrl}}/goods_prize_content +Content-Type: {{contentType}} +Authorization: Bearer {{authToken}} + +{ + "goods_id": {{testGoodsId}}, + "num": 0 +} + +### ============================================ +### 8. 中奖记录接口 (GoodsController) +### Requirements: 8.1-8.4 +### ============================================ + +### 8.1 获取中奖记录 - 全部分类 +# POST /api/goods_prize_logs +# Requirements: 8.1, 8.3 +POST {{baseUrl}}/goods_prize_logs +Content-Type: {{contentType}} +Authorization: Bearer {{authToken}} + +{ + "goods_id": {{testGoodsId}}, + "goods_num": {{testGoodsNum}}, + "shang_id": 0, + "page": 1, + "pageSize": 15 +} + +### 8.2 获取中奖记录 - 按分类过滤 +# POST /api/goods_prize_logs +# Requirements: 8.1, 8.2 +POST {{baseUrl}}/goods_prize_logs +Content-Type: {{contentType}} +Authorization: Bearer {{authToken}} + +{ + "goods_id": {{testGoodsId}}, + "goods_num": {{testGoodsNum}}, + "shang_id": {{testShangId}}, + "page": 1, + "pageSize": 15 +} + +### ============================================ +### 9. 收藏接口 (CollectionController) +### Requirements: 6.1-6.4 +### ============================================ + +### 9.1 收藏/取消收藏商品 +# POST /api/addCollect +# Requirements: 6.1-6.3 +POST {{baseUrl}}/addCollect +Content-Type: {{contentType}} +Authorization: Bearer {{authToken}} + +{ + "goods_id": {{testGoodsId}}, + "goods_num": {{testGoodsNum}} +} + +### 9.2 获取收藏列表 - 全部类型 +# POST /api/listCollect +# Requirements: 6.4 +POST {{baseUrl}}/listCollect +Content-Type: {{contentType}} +Authorization: Bearer {{authToken}} + +{ + "type": -1, + "page": 1, + "pageSize": 100 +} + +### 9.3 获取收藏列表 - 按类型过滤(一番赏) +# POST /api/listCollect +# Requirements: 6.4 +POST {{baseUrl}}/listCollect +Content-Type: {{contentType}} +Authorization: Bearer {{authToken}} + +{ + "type": 1, + "page": 1, + "pageSize": 100 +} + +### 9.4 删除收藏 +# POST /api/delCollect +# 注意:需要替换为有效的收藏ID +POST {{baseUrl}}/delCollect +Content-Type: {{contentType}} +Authorization: Bearer {{authToken}} + +{ + "id": 1 +} + +### ============================================ +### 10. 错误场景测试 +### ============================================ + +### 10.1 未授权访问 - 获取商品列表(无Token) +POST {{baseUrl}}/goods_list +Content-Type: {{contentType}} + +{ + "type": -1, + "page": 1, + "pageSize": 15 +} + +### 10.2 未授权访问 - 获取商品详情(无Token) +POST {{baseUrl}}/goods_detail +Content-Type: {{contentType}} + +{ + "goods_id": {{testGoodsId}}, + "goods_num": 0 +} + +### 10.3 未授权访问 - 收藏商品(无Token) +POST {{baseUrl}}/addCollect +Content-Type: {{contentType}} + +{ + "goods_id": {{testGoodsId}}, + "goods_num": {{testGoodsNum}} +} + +### 10.4 未授权访问 - 获取收藏列表(无Token) +POST {{baseUrl}}/listCollect +Content-Type: {{contentType}} + +{ + "type": -1, + "page": 1, + "pageSize": 100 +} + +### 10.5 参数缺失 - 商品详情(无商品ID) +POST {{baseUrl}}/goods_detail +Content-Type: {{contentType}} +Authorization: Bearer {{authToken}} + +{} + +### 10.6 参数缺失 - 商品详情(商品ID为0) +POST {{baseUrl}}/goods_detail +Content-Type: {{contentType}} +Authorization: Bearer {{authToken}} + +{ + "goods_id": 0, + "goods_num": 0 +} + +### 10.7 参数缺失 - 子奖品(无商品ID) +POST {{baseUrl}}/goods_children +Content-Type: {{contentType}} +Authorization: Bearer {{authToken}} + +{ + "goods_num": {{testGoodsNum}}, + "goods_list_id": {{testGoodsListId}} +} + +### 10.8 参数缺失 - 子奖品(无奖品列表ID) +POST {{baseUrl}}/goods_children +Content-Type: {{contentType}} +Authorization: Bearer {{authToken}} + +{ + "goods_id": {{testGoodsId}}, + "goods_num": {{testGoodsNum}} +} + +### 10.9 参数缺失 - 扩展配置(无商品ID) +POST {{baseUrl}}/goods_extend +Content-Type: {{contentType}} +Authorization: Bearer {{authToken}} + +{ + "goods_type": 1 +} + +### 10.10 参数缺失 - 箱号列表(无商品ID) +POST {{baseUrl}}/goods_num_list +Content-Type: {{contentType}} +Authorization: Bearer {{authToken}} + +{} + +### 10.11 参数缺失 - 奖品统计(无商品ID) +POST {{baseUrl}}/goods_prize_count +Content-Type: {{contentType}} +Authorization: Bearer {{authToken}} + +{} + +### 10.12 参数缺失 - 收藏商品(无商品ID) +POST {{baseUrl}}/addCollect +Content-Type: {{contentType}} +Authorization: Bearer {{authToken}} + +{ + "goods_num": {{testGoodsNum}} +} + +### 10.13 参数缺失 - 删除收藏(无收藏ID) +POST {{baseUrl}}/delCollect +Content-Type: {{contentType}} +Authorization: Bearer {{authToken}} + +{} + diff --git a/server/C#/HoneyBox/src/HoneyBox.Api/obj/Debug/net10.0/HoneyBox.Api.AssemblyInfo.cs b/server/C#/HoneyBox/src/HoneyBox.Api/obj/Debug/net10.0/HoneyBox.Api.AssemblyInfo.cs index 6c299830..558d38ec 100644 --- a/server/C#/HoneyBox/src/HoneyBox.Api/obj/Debug/net10.0/HoneyBox.Api.AssemblyInfo.cs +++ b/server/C#/HoneyBox/src/HoneyBox.Api/obj/Debug/net10.0/HoneyBox.Api.AssemblyInfo.cs @@ -13,7 +13,7 @@ using System.Reflection; [assembly: System.Reflection.AssemblyCompanyAttribute("HoneyBox.Api")] [assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")] [assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")] -[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+21c4bb5c6a0c35c345420f3a29d64fd2922e7427")] +[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+7e00d28ad48928059f0ff514ed804f2cb0626442")] [assembly: System.Reflection.AssemblyProductAttribute("HoneyBox.Api")] [assembly: System.Reflection.AssemblyTitleAttribute("HoneyBox.Api")] [assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")] diff --git a/server/C#/HoneyBox/src/HoneyBox.Api/obj/Debug/net10.0/HoneyBox.Api.dll b/server/C#/HoneyBox/src/HoneyBox.Api/obj/Debug/net10.0/HoneyBox.Api.dll index 7fd04533..9e781166 100644 Binary files a/server/C#/HoneyBox/src/HoneyBox.Api/obj/Debug/net10.0/HoneyBox.Api.dll and b/server/C#/HoneyBox/src/HoneyBox.Api/obj/Debug/net10.0/HoneyBox.Api.dll differ diff --git a/server/C#/HoneyBox/src/HoneyBox.Api/obj/Debug/net10.0/HoneyBox.Api.pdb b/server/C#/HoneyBox/src/HoneyBox.Api/obj/Debug/net10.0/HoneyBox.Api.pdb index 258e7863..02651cb2 100644 Binary files a/server/C#/HoneyBox/src/HoneyBox.Api/obj/Debug/net10.0/HoneyBox.Api.pdb and b/server/C#/HoneyBox/src/HoneyBox.Api/obj/Debug/net10.0/HoneyBox.Api.pdb differ diff --git a/server/C#/HoneyBox/src/HoneyBox.Api/obj/Debug/net10.0/apphost.exe b/server/C#/HoneyBox/src/HoneyBox.Api/obj/Debug/net10.0/apphost.exe index f88859cd..23d27f01 100644 Binary files a/server/C#/HoneyBox/src/HoneyBox.Api/obj/Debug/net10.0/apphost.exe and b/server/C#/HoneyBox/src/HoneyBox.Api/obj/Debug/net10.0/apphost.exe differ diff --git a/server/C#/HoneyBox/src/HoneyBox.Api/obj/Debug/net10.0/ref/HoneyBox.Api.dll b/server/C#/HoneyBox/src/HoneyBox.Api/obj/Debug/net10.0/ref/HoneyBox.Api.dll index 7081e478..f265e9e4 100644 Binary files a/server/C#/HoneyBox/src/HoneyBox.Api/obj/Debug/net10.0/ref/HoneyBox.Api.dll and b/server/C#/HoneyBox/src/HoneyBox.Api/obj/Debug/net10.0/ref/HoneyBox.Api.dll differ diff --git a/server/C#/HoneyBox/src/HoneyBox.Api/obj/Debug/net10.0/refint/HoneyBox.Api.dll b/server/C#/HoneyBox/src/HoneyBox.Api/obj/Debug/net10.0/refint/HoneyBox.Api.dll index 7081e478..f265e9e4 100644 Binary files a/server/C#/HoneyBox/src/HoneyBox.Api/obj/Debug/net10.0/refint/HoneyBox.Api.dll and b/server/C#/HoneyBox/src/HoneyBox.Api/obj/Debug/net10.0/refint/HoneyBox.Api.dll differ diff --git a/server/C#/HoneyBox/src/HoneyBox.Api/obj/Debug/net10.0/rjsmcshtml.dswa.cache.json b/server/C#/HoneyBox/src/HoneyBox.Api/obj/Debug/net10.0/rjsmcshtml.dswa.cache.json index 39ad210d..175c21b8 100644 --- a/server/C#/HoneyBox/src/HoneyBox.Api/obj/Debug/net10.0/rjsmcshtml.dswa.cache.json +++ b/server/C#/HoneyBox/src/HoneyBox.Api/obj/Debug/net10.0/rjsmcshtml.dswa.cache.json @@ -1 +1 @@ -{"GlobalPropertiesHash":"Ysh/n1uypAtQviJNGODCPwxAly+bcTyiCeqRgFpIiaE=","FingerprintPatternsHash":"gq3WsqcKBUGTSNle7RKKyXRIwh7M8ccEqOqYvIzoM04=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["KmCbcplRcUDHmkghC2lKt4atEvqmdCypecbGKiyOL1Y=","Yr\u002BOWR3TPK4EWzVknKsBQAY40K9td9F\u002BJRMqIuPwWE0=","iwh6YCm1hP7gw5T3HlRwNBcUu9NVDH4VUC1D2LbVP6Y=","A1eOW5m2OjAzDYXt5hE8LFSp5KPpS853XPPu1pJgO7M=","0HeClgJNdUHftrAxD1WHyxDsoBBIMgh7pzhFVwvPzCk=","ug\u002BeY\u002Bhf4RfN5v1Kpje6K\u002BfkdoRsX4hw/oT\u002Bxe0stT4=","Ff7GKRhN1a2md5IaW2AMR/j8y6P7n3aDr8WZCWUaC6o=","7mYTjVVc3QiwJS21WoZe/uABKsCqwZFgh21t86u3bEo="],"CachedAssets":{},"CachedCopyCandidates":{}} \ No newline at end of file +{"GlobalPropertiesHash":"Ysh/n1uypAtQviJNGODCPwxAly+bcTyiCeqRgFpIiaE=","FingerprintPatternsHash":"gq3WsqcKBUGTSNle7RKKyXRIwh7M8ccEqOqYvIzoM04=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["KmCbcplRcUDHmkghC2lKt4atEvqmdCypecbGKiyOL1Y=","Yr\u002BOWR3TPK4EWzVknKsBQAY40K9td9F\u002BJRMqIuPwWE0=","iwh6YCm1hP7gw5T3HlRwNBcUu9NVDH4VUC1D2LbVP6Y=","j7KwQ7vZvdcRd5zgXvB4eGt8NbUUSSPUUhkRceBkb1s=","A1eOW5m2OjAzDYXt5hE8LFSp5KPpS853XPPu1pJgO7M=","MzSV86jgVStZOroCGPZnD7UWQ8g2CiyPQ0SO8zLA3TM=","ug\u002BeY\u002Bhf4RfN5v1Kpje6K\u002BfkdoRsX4hw/oT\u002Bxe0stT4=","Ff7GKRhN1a2md5IaW2AMR/j8y6P7n3aDr8WZCWUaC6o=","FnpzMeeyaOTZN\u002BxilFV3aDYmU2xYJ/3/CY39GC865P8="],"CachedAssets":{},"CachedCopyCandidates":{}} \ No newline at end of file diff --git a/server/C#/HoneyBox/src/HoneyBox.Api/obj/Debug/net10.0/rjsmrazor.dswa.cache.json b/server/C#/HoneyBox/src/HoneyBox.Api/obj/Debug/net10.0/rjsmrazor.dswa.cache.json index a659ef8d..d5e9ec59 100644 --- a/server/C#/HoneyBox/src/HoneyBox.Api/obj/Debug/net10.0/rjsmrazor.dswa.cache.json +++ b/server/C#/HoneyBox/src/HoneyBox.Api/obj/Debug/net10.0/rjsmrazor.dswa.cache.json @@ -1 +1 @@ -{"GlobalPropertiesHash":"nOa+ffacSShxUVXaHMpxAJqQMESoIWNWaEHTlS9nGGU=","FingerprintPatternsHash":"gq3WsqcKBUGTSNle7RKKyXRIwh7M8ccEqOqYvIzoM04=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["KmCbcplRcUDHmkghC2lKt4atEvqmdCypecbGKiyOL1Y=","Yr\u002BOWR3TPK4EWzVknKsBQAY40K9td9F\u002BJRMqIuPwWE0=","iwh6YCm1hP7gw5T3HlRwNBcUu9NVDH4VUC1D2LbVP6Y=","A1eOW5m2OjAzDYXt5hE8LFSp5KPpS853XPPu1pJgO7M=","0HeClgJNdUHftrAxD1WHyxDsoBBIMgh7pzhFVwvPzCk=","ug\u002BeY\u002Bhf4RfN5v1Kpje6K\u002BfkdoRsX4hw/oT\u002Bxe0stT4=","Ff7GKRhN1a2md5IaW2AMR/j8y6P7n3aDr8WZCWUaC6o=","7mYTjVVc3QiwJS21WoZe/uABKsCqwZFgh21t86u3bEo="],"CachedAssets":{},"CachedCopyCandidates":{}} \ No newline at end of file +{"GlobalPropertiesHash":"nOa+ffacSShxUVXaHMpxAJqQMESoIWNWaEHTlS9nGGU=","FingerprintPatternsHash":"gq3WsqcKBUGTSNle7RKKyXRIwh7M8ccEqOqYvIzoM04=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["KmCbcplRcUDHmkghC2lKt4atEvqmdCypecbGKiyOL1Y=","Yr\u002BOWR3TPK4EWzVknKsBQAY40K9td9F\u002BJRMqIuPwWE0=","iwh6YCm1hP7gw5T3HlRwNBcUu9NVDH4VUC1D2LbVP6Y=","j7KwQ7vZvdcRd5zgXvB4eGt8NbUUSSPUUhkRceBkb1s=","A1eOW5m2OjAzDYXt5hE8LFSp5KPpS853XPPu1pJgO7M=","MzSV86jgVStZOroCGPZnD7UWQ8g2CiyPQ0SO8zLA3TM=","ug\u002BeY\u002Bhf4RfN5v1Kpje6K\u002BfkdoRsX4hw/oT\u002Bxe0stT4=","Ff7GKRhN1a2md5IaW2AMR/j8y6P7n3aDr8WZCWUaC6o=","FnpzMeeyaOTZN\u002BxilFV3aDYmU2xYJ/3/CY39GC865P8="],"CachedAssets":{},"CachedCopyCandidates":{}} \ No newline at end of file diff --git a/server/C#/HoneyBox/src/HoneyBox.Core/Interfaces/ICollectionService.cs b/server/C#/HoneyBox/src/HoneyBox.Core/Interfaces/ICollectionService.cs new file mode 100644 index 00000000..8bdb9fb9 --- /dev/null +++ b/server/C#/HoneyBox/src/HoneyBox.Core/Interfaces/ICollectionService.cs @@ -0,0 +1,46 @@ +using HoneyBox.Model.Models; +using HoneyBox.Model.Models.Goods; + +namespace HoneyBox.Core.Interfaces; + +/// +/// 收藏服务接口 +/// +public interface ICollectionService +{ + /// + /// 收藏/取消收藏商品 (PHP addCollect - 切换收藏状态) + /// + /// 用户ID + /// 商品ID + /// 箱号 + /// 操作是否成功 + Task ToggleCollectionAsync(int userId, int goodsId, int goodsNum); + + /// + /// 获取收藏列表 (PHP listCollect) + /// + /// 用户ID + /// 商品类型 (0=全部) + /// 页码 + /// 每页数量 + /// 分页收藏列表 + Task GetCollectionListAsync(int userId, int type, int page, int pageSize); + + /// + /// 删除收藏 (PHP delCollect) + /// + /// 用户ID + /// 收藏记录ID + /// 操作是否成功 + Task DeleteCollectionAsync(int userId, int collectionId); + + /// + /// 检查是否已收藏 + /// + /// 用户ID + /// 商品ID + /// 箱号 + /// 是否已收藏 + Task IsCollectedAsync(int userId, int goodsId, int goodsNum); +} diff --git a/server/C#/HoneyBox/src/HoneyBox.Core/Interfaces/IGoodsCacheService.cs b/server/C#/HoneyBox/src/HoneyBox.Core/Interfaces/IGoodsCacheService.cs new file mode 100644 index 00000000..25a344ab --- /dev/null +++ b/server/C#/HoneyBox/src/HoneyBox.Core/Interfaces/IGoodsCacheService.cs @@ -0,0 +1,66 @@ +namespace HoneyBox.Core.Interfaces; + +/// +/// 商品缓存服务接口 +/// +public interface IGoodsCacheService +{ + /// + /// 获取商品参与次数 + /// + /// 商品ID + /// 参与次数,如果缓存不存在返回-1 + Task GetJoinCountAsync(int goodsId); + + /// + /// 设置商品参与次数 + /// + /// 商品ID + /// 参与次数 + Task SetJoinCountAsync(int goodsId, int count); + + /// + /// 增加商品参与次数 + /// + /// 商品ID + /// 增加后的参与次数 + Task IncrementJoinCountAsync(int goodsId); + + /// + /// 清除商品缓存 + /// + /// 商品ID + Task InvalidateGoodsCacheAsync(int goodsId); + + /// + /// 获取商品列表缓存 + /// + /// 缓存键 + /// 缓存的商品列表JSON,如果不存在返回null + Task GetGoodsListCacheAsync(string cacheKey); + + /// + /// 设置商品列表缓存 + /// + /// 缓存键 + /// 商品列表JSON + /// 过期时间 + Task SetGoodsListCacheAsync(string cacheKey, string data, TimeSpan expiry); + + /// + /// 获取商品详情缓存 + /// + /// 商品ID + /// 箱号 + /// 缓存的商品详情JSON,如果不存在返回null + Task GetGoodsDetailCacheAsync(int goodsId, int goodsNum); + + /// + /// 设置商品详情缓存 + /// + /// 商品ID + /// 箱号 + /// 商品详情JSON + /// 过期时间 + Task SetGoodsDetailCacheAsync(int goodsId, int goodsNum, string data, TimeSpan expiry); +} diff --git a/server/C#/HoneyBox/src/HoneyBox.Core/Interfaces/IGoodsService.cs b/server/C#/HoneyBox/src/HoneyBox.Core/Interfaces/IGoodsService.cs new file mode 100644 index 00000000..67cb4070 --- /dev/null +++ b/server/C#/HoneyBox/src/HoneyBox.Core/Interfaces/IGoodsService.cs @@ -0,0 +1,60 @@ +using HoneyBox.Model.Models; +using HoneyBox.Model.Models.Goods; + +namespace HoneyBox.Core.Interfaces; + +/// +/// 商品服务接口 +/// +public interface IGoodsService +{ + /// + /// 获取商品列表 + /// + /// 查询请求 + /// 用户ID + /// 分页商品列表 + Task> GetGoodsListAsync(GoodsListRequest request, int userId); + + /// + /// 获取商品详情 + /// + /// 商品ID + /// 箱号 (0表示自动选择) + /// 用户ID + /// 商品详情 + Task GetGoodsDetailAsync(int goodsId, int goodsNum, int userId); + + /// + /// 获取商品子奖品列表 + /// + /// 商品ID + /// 箱号 + /// 奖品列表ID + /// 子奖品列表 + Task> GetGoodsChildrenAsync(int goodsId, int goodsNum, int goodsListId); + + /// + /// 获取商品扩展配置 + /// + /// 商品ID + /// 商品类型 + /// 扩展配置 + Task GetGoodsExtendAsync(int goodsId, int goodsType); + + /// + /// 获取箱号列表 + /// + /// 商品ID + /// 箱号分组列表 + Task> GetBoxListAsync(int goodsId); + + /// + /// 获取箱号详情 + /// + /// 商品ID + /// 页码 + /// 排序方式: 0=箱号升序, 1=箱号降序, 2=余量降序 + /// 箱号详情列表 + Task> GetBoxDetailAsync(int goodsId, int pageNo, int sort); +} diff --git a/server/C#/HoneyBox/src/HoneyBox.Core/Interfaces/IPrizeService.cs b/server/C#/HoneyBox/src/HoneyBox.Core/Interfaces/IPrizeService.cs new file mode 100644 index 00000000..23fd7d8f --- /dev/null +++ b/server/C#/HoneyBox/src/HoneyBox.Core/Interfaces/IPrizeService.cs @@ -0,0 +1,35 @@ +using HoneyBox.Model.Models.Goods; + +namespace HoneyBox.Core.Interfaces; + +/// +/// 奖品服务接口 +/// +public interface IPrizeService +{ + /// + /// 获取奖品数量统计 + /// + /// 商品ID + /// 奖品数量统计 + Task GetPrizeCountAsync(int goodsId); + + /// + /// 获取奖品内容 + /// + /// 商品ID + /// 箱号 + /// 奖品内容 + Task GetPrizeContentAsync(int goodsId, int num); + + /// + /// 获取中奖记录 + /// + /// 商品ID + /// 箱号 + /// 赏品分类ID (0表示全部) + /// 页码 + /// 每页数量 + /// 中奖记录 + Task GetPrizeLogsAsync(int goodsId, int goodsNum, int shangId, int page, int pageSize); +} diff --git a/server/C#/HoneyBox/src/HoneyBox.Core/Services/CollectionService.cs b/server/C#/HoneyBox/src/HoneyBox.Core/Services/CollectionService.cs new file mode 100644 index 00000000..a4bc488a --- /dev/null +++ b/server/C#/HoneyBox/src/HoneyBox.Core/Services/CollectionService.cs @@ -0,0 +1,257 @@ +using HoneyBox.Core.Interfaces; +using HoneyBox.Model.Data; +using HoneyBox.Model.Entities; +using HoneyBox.Model.Models; +using HoneyBox.Model.Models.Goods; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; + +namespace HoneyBox.Core.Services; + +/// +/// 收藏服务实现 +/// +public class CollectionService : ICollectionService +{ + private readonly HoneyBoxDbContext _dbContext; + private readonly ILogger _logger; + private readonly IRedisService _redisService; + + // 奖品统计ID范围 (与PHP保持一致) + private static readonly int[] ShangPrizeIdRange = { 10, 33 }; + + public CollectionService( + HoneyBoxDbContext dbContext, + ILogger logger, + IRedisService redisService) + { + _dbContext = dbContext; + _logger = logger; + _redisService = redisService; + } + + /// + public async Task ToggleCollectionAsync(int userId, int goodsId, int goodsNum) + { + // 1. 验证商品是否存在 + var goods = await _dbContext.Goods + .Where(g => g.Id == goodsId) + .Select(g => new { g.Id, g.Stock, g.Status, g.Type }) + .FirstOrDefaultAsync(); + + if (goods == null) + { + throw new InvalidOperationException("盒子不存在"); + } + + if (goods.Status != 1 && goods.Status != 3) + { + throw new InvalidOperationException("盒子已下架"); + } + + // 2. 检查是否已收藏 + var existingCollection = await _dbContext.GoodsCollections + .Where(c => c.UserId == userId && c.GoodsId == goodsId && c.Num == goodsNum) + .FirstOrDefaultAsync(); + + bool result; + if (existingCollection != null) + { + // 已收藏,则取消收藏 + _dbContext.GoodsCollections.Remove(existingCollection); + result = await _dbContext.SaveChangesAsync() > 0; + } + else + { + // 未收藏,则添加收藏 + var collection = new GoodsCollection + { + UserId = userId, + GoodsId = goodsId, + Num = goodsNum, + Type = goods.Type, + CreatedAt = DateTime.Now + }; + _dbContext.GoodsCollections.Add(collection); + result = await _dbContext.SaveChangesAsync() > 0; + } + + // 3. 清除相关缓存 + if (result) + { + try + { + await _redisService.DeleteAsync($"goods_detail_{goodsId}_{userId}"); + await _redisService.DeleteAsync($"infinite_goodsdetail_{goodsId}_{userId}"); + } + catch (Exception ex) + { + _logger.LogWarning(ex, "清除收藏相关缓存失败"); + } + } + + return result; + } + + /// + public async Task GetCollectionListAsync(int userId, int type, int page, int pageSize) + { + // 1. 构建查询 + var query = _dbContext.GoodsCollections + .Where(c => c.UserId == userId); + + // 按类型过滤 + if (type > 0) + { + query = query.Where(c => c.Type == type); + } + + // 2. 获取总数和分页数据 + var totalCount = await query.CountAsync(); + var lastPage = pageSize > 0 ? (int)Math.Ceiling((double)totalCount / pageSize) : 1; + + var collections = await query + .OrderByDescending(c => c.Id) + .Skip((page - 1) * pageSize) + .Take(pageSize) + .ToListAsync(); + + if (!collections.Any()) + { + return new CollectionListResponse + { + Data = new List(), + LastPage = lastPage + }; + } + + // 3. 获取商品信息 + var goodsIds = collections.Select(c => c.GoodsId).Distinct().ToList(); + var goodsDict = await _dbContext.Goods + .Where(g => goodsIds.Contains(g.Id)) + .Select(g => new { g.Id, g.Title, g.Price, g.ImgUrl }) + .ToDictionaryAsync(g => g.Id); + + // 4. 获取库存信息 (仅对特定类型的商品) + // 类型 1, 3, 5, 6, 10, 11 需要查询库存 + var stockTypes = new byte[] { 1, 3, 5, 6, 10, 11 }; + var stockCollections = collections + .Where(c => stockTypes.Contains(c.Type)) + .Select(c => new { c.GoodsId, c.Num }) + .Distinct() + .ToList(); + + var stockDict = new Dictionary(); + if (stockCollections.Any()) + { + foreach (var sc in stockCollections) + { + var stockInfo = await _dbContext.GoodsItems + .Where(gi => gi.GoodsId == sc.GoodsId + && gi.Num == sc.Num + && gi.ShangId >= ShangPrizeIdRange[0] + && gi.ShangId <= ShangPrizeIdRange[1]) + .GroupBy(gi => 1) + .Select(g => new + { + Stock = g.Sum(x => x.Stock), + SurplusStock = g.Sum(x => x.SurplusStock) + }) + .FirstOrDefaultAsync(); + + var key = $"{sc.GoodsId}_{sc.Num}"; + stockDict[key] = stockInfo != null + ? (stockInfo.Stock, stockInfo.SurplusStock) + : (0, 0); + } + } + + // 5. 构建响应 + var result = new List(); + foreach (var collection in collections) + { + var dto = new CollectionDto + { + Id = collection.Id, + GoodsId = collection.GoodsId, + Type = collection.Type, + Num = collection.Num + }; + + // 填充商品信息 + if (goodsDict.TryGetValue(collection.GoodsId, out var goodsInfo)) + { + dto.GoodsTitle = goodsInfo.Title; + dto.GoodsPrice = goodsInfo.Price.ToString("0.##"); + dto.ImgUrl = FormatImageUrl(goodsInfo.ImgUrl); + } + + // 填充库存信息 + if (stockTypes.Contains(collection.Type)) + { + var stockKey = $"{collection.GoodsId}_{collection.Num}"; + if (stockDict.TryGetValue(stockKey, out var stock)) + { + dto.Stock = stock.Stock; + dto.SurplusStock = stock.SurplusStock; + } + } + + result.Add(dto); + } + + return new CollectionListResponse + { + Data = result, + LastPage = lastPage + }; + } + + /// + public async Task DeleteCollectionAsync(int userId, int collectionId) + { + var collection = await _dbContext.GoodsCollections + .Where(c => c.UserId == userId && c.Id == collectionId) + .FirstOrDefaultAsync(); + + if (collection == null) + { + throw new InvalidOperationException("请求重复操作"); + } + + _dbContext.GoodsCollections.Remove(collection); + return await _dbContext.SaveChangesAsync() > 0; + } + + /// + public async Task IsCollectedAsync(int userId, int goodsId, int goodsNum) + { + if (userId <= 0) + { + return false; + } + + return await _dbContext.GoodsCollections + .AnyAsync(c => c.UserId == userId && c.GoodsId == goodsId && c.Num == goodsNum); + } + + /// + /// 格式化图片URL + /// + private static string FormatImageUrl(string? imgUrl) + { + if (string.IsNullOrEmpty(imgUrl)) + { + return string.Empty; + } + + // 如果已经是完整URL,直接返回 + if (imgUrl.StartsWith("http://") || imgUrl.StartsWith("https://")) + { + return imgUrl; + } + + // 否则直接返回 (可以从配置读取基础URL) + return imgUrl; + } +} diff --git a/server/C#/HoneyBox/src/HoneyBox.Core/Services/GoodsCacheService.cs b/server/C#/HoneyBox/src/HoneyBox.Core/Services/GoodsCacheService.cs new file mode 100644 index 00000000..ca1e671b --- /dev/null +++ b/server/C#/HoneyBox/src/HoneyBox.Core/Services/GoodsCacheService.cs @@ -0,0 +1,114 @@ +using HoneyBox.Core.Interfaces; +using Microsoft.Extensions.Logging; + +namespace HoneyBox.Core.Services; + +/// +/// 商品缓存服务实现 +/// +public class GoodsCacheService : IGoodsCacheService +{ + private readonly IRedisService _redisService; + private readonly ILogger _logger; + + // 缓存键前缀 - 与PHP保持一致 + private const string JoinCountKeyPrefix = "order_goods_count:"; + private const string GoodsListKeyPrefix = "goods_list_"; + private const string GoodsDetailKeyPrefix = "goods_detail_"; + + // 缓存过期时间 + private static readonly TimeSpan JoinCountExpiry = TimeSpan.FromMinutes(5); + private static readonly TimeSpan GoodsListExpiry = TimeSpan.FromSeconds(30); + private static readonly TimeSpan GoodsDetailExpiry = TimeSpan.FromMinutes(5); + + public GoodsCacheService( + IRedisService redisService, + ILogger logger) + { + _redisService = redisService; + _logger = logger; + } + + /// + public async Task GetJoinCountAsync(int goodsId) + { + var key = $"{JoinCountKeyPrefix}{goodsId}"; + var value = await _redisService.GetStringAsync(key); + + if (int.TryParse(value, out var count)) + { + return count; + } + + // 返回-1表示缓存不存在 + return -1; + } + + /// + public async Task SetJoinCountAsync(int goodsId, int count) + { + var key = $"{JoinCountKeyPrefix}{goodsId}"; + await _redisService.SetStringAsync(key, count.ToString(), JoinCountExpiry); + } + + /// + public async Task IncrementJoinCountAsync(int goodsId) + { + var key = $"{JoinCountKeyPrefix}{goodsId}"; + var currentValue = await GetJoinCountAsync(goodsId); + var newValue = currentValue > 0 ? currentValue + 1 : 1; + + await _redisService.SetStringAsync(key, newValue.ToString(), JoinCountExpiry); + + return newValue; + } + + /// + public async Task InvalidateGoodsCacheAsync(int goodsId) + { + // 清除参与次数缓存 + var joinCountKey = $"{JoinCountKeyPrefix}{goodsId}"; + await _redisService.DeleteAsync(joinCountKey); + + // 清除商品详情缓存 - 由于箱号可能有多个,这里清除所有可能的箱号缓存 + // 注意:实际生产环境中可能需要使用Redis的SCAN命令来批量删除 + // 这里简化处理,只清除常见的箱号范围(0-100) + var deleteTasks = new List(); + for (int num = 0; num <= 100; num++) + { + var detailKey = $"{GoodsDetailKeyPrefix}{goodsId}_{num}"; + deleteTasks.Add(_redisService.DeleteAsync(detailKey)); + } + await Task.WhenAll(deleteTasks); + + _logger.LogInformation("已清除商品 {GoodsId} 的所有缓存(参与次数、商品详情)", goodsId); + } + + /// + public async Task GetGoodsListCacheAsync(string cacheKey) + { + var key = $"{GoodsListKeyPrefix}{cacheKey}"; + return await _redisService.GetStringAsync(key); + } + + /// + public async Task SetGoodsListCacheAsync(string cacheKey, string data, TimeSpan expiry) + { + var key = $"{GoodsListKeyPrefix}{cacheKey}"; + await _redisService.SetStringAsync(key, data, expiry); + } + + /// + public async Task GetGoodsDetailCacheAsync(int goodsId, int goodsNum) + { + var key = $"{GoodsDetailKeyPrefix}{goodsId}_{goodsNum}"; + return await _redisService.GetStringAsync(key); + } + + /// + public async Task SetGoodsDetailCacheAsync(int goodsId, int goodsNum, string data, TimeSpan expiry) + { + var key = $"{GoodsDetailKeyPrefix}{goodsId}_{goodsNum}"; + await _redisService.SetStringAsync(key, data, expiry); + } +} diff --git a/server/C#/HoneyBox/src/HoneyBox.Core/Services/GoodsService.cs b/server/C#/HoneyBox/src/HoneyBox.Core/Services/GoodsService.cs new file mode 100644 index 00000000..6b80a633 --- /dev/null +++ b/server/C#/HoneyBox/src/HoneyBox.Core/Services/GoodsService.cs @@ -0,0 +1,1136 @@ +using HoneyBox.Core.Interfaces; +using HoneyBox.Model.Data; +using HoneyBox.Model.Models; +using HoneyBox.Model.Models.Goods; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using System.Text.Json; + +namespace HoneyBox.Core.Services; + +/// +/// 商品服务实现 +/// +public class GoodsService : IGoodsService +{ + private readonly HoneyBoxDbContext _dbContext; + private readonly IGoodsCacheService _cacheService; + private readonly ILogger _logger; + + // 抽奖赏品ID范围 [10, 33] + private static readonly int[] ShangPrizeIdRange = { 10, 33 }; + // 统计次数赏品ID范围 [10, 38] + private static readonly int[] ShangCountIdRange = { 10, 38 }; + // 赠送赏品ID (不计入概率) + private static readonly int[] ShangGiveIds = { 1, 2, 3, 4, 5 }; + + public GoodsService( + HoneyBoxDbContext dbContext, + IGoodsCacheService cacheService, + ILogger logger) + { + _dbContext = dbContext; + _cacheService = cacheService; + _logger = logger; + } + + /// + public async Task> GetGoodsListAsync(GoodsListRequest request, int userId) + { + var page = request.Page > 0 ? request.Page : 1; + var pageSize = request.PageSize > 0 ? request.PageSize : 15; + var type = request.Type; + + // Type 10 特殊分页 + if (type == 10) + { + pageSize = 999; + } + + // 构建基础查询 + var query = _dbContext.Goods + .Where(g => g.Status == 1 && g.ShowIs == 0); + + // 类型过滤 + query = ApplyTypeFilter(query, type); + + // 解锁金额过滤 + query = await ApplyUnlockAmountFilterAsync(query, userId); + + // 获取商品类型映射 + var goodsTypesMap = await GetGoodsTypesMapAsync(); + + // 获取总数 + var total = await query.CountAsync(); + + // 排序和分页 + var goods = await query + .OrderByDescending(g => g.Sort) + .ThenByDescending(g => g.Id) + .Skip((page - 1) * pageSize) + .Take(pageSize) + .Select(g => new + { + g.Id, + g.Title, + g.ImgUrl, + g.Price, + g.Type, + g.Stock, + g.SaleStock, + g.Status, + g.LockIs, + g.IsShouZhe, + g.NewIs + }) + .ToListAsync(); + + if (!goods.Any()) + { + return new PageResponse + { + Data = new List(), + Total = 0, + Page = page, + PageSize = pageSize, + LastPage = 0 + }; + } + + // 获取所有商品ID + var goodsIds = goods.Select(g => g.Id).ToList(); + + // 批量获取Type=10商品的库存信息 + var type10GoodsIds = goods.Where(g => g.Type == 10).Select(g => g.Id).ToList(); + var goodsListStockMap = new Dictionary(); + + if (type10GoodsIds.Any()) + { + var stockData = await _dbContext.GoodsItems + .Where(gi => type10GoodsIds.Contains(gi.GoodsId) + && gi.Num == 1 + && gi.ShangId >= ShangPrizeIdRange[0] + && gi.ShangId <= ShangPrizeIdRange[1]) + .GroupBy(gi => gi.GoodsId) + .Select(g => new + { + GoodsId = g.Key, + Stock = g.Sum(x => x.Stock), + SurplusStock = g.Sum(x => x.SurplusStock) + }) + .ToListAsync(); + + foreach (var item in stockData) + { + goodsListStockMap[item.GoodsId] = (item.Stock, item.SurplusStock); + } + } + + // 构建结果列表 + var result = new List(); + foreach (var g in goods) + { + var dto = new GoodsListDto + { + Id = g.Id, + Title = g.Title, + ImgUrl = FormatImageUrl(g.ImgUrl), + Price = g.Price.ToString("0.##"), + Type = g.Type, + Stock = g.Stock, + SaleStock = g.Stock - g.SaleStock, // 剩余库存 + Status = g.Status, + LockIs = g.LockIs, + IsShouZhe = g.IsShouZhe, + NewIs = g.NewIs, + NeedDrawNum = g.Type == 7 ? 1 : 0 + }; + + // Type=10 特殊处理库存 + if (g.Type == 10 && goodsListStockMap.TryGetValue(g.Id, out var stockInfo)) + { + dto.Stock = stockInfo.Stock; + dto.SaleStock = stockInfo.SurplusStock; + } + + // 获取参与次数 + dto.JoinCount = await GetJoinCountAsync(g.Id, 1, g.Type); + + // 设置类型文字 + if (goodsTypesMap.TryGetValue(g.Type, out var typeText)) + { + dto.TypeText = typeText; + } + + result.Add(dto); + } + + var lastPage = pageSize > 0 ? (int)Math.Ceiling((double)total / pageSize) : 0; + return new PageResponse + { + Data = result, + Total = total, + Page = page, + PageSize = pageSize, + LastPage = lastPage + }; + } + + /// + /// 应用类型过滤 + /// + private IQueryable ApplyTypeFilter(IQueryable query, int type) + { + // 类型映射 - 使用 List 避免 ReadOnlySpan 与 EF Core InMemory 的兼容性问题 + var typeMapping = new Dictionary> + { + { 1, new List { 1 } }, + { 2, new List { 2 } }, + { 3, new List { 3 } }, + { 5, new List { 5 } }, + { 6, new List { 6 } }, + { 7, new List { 7 } }, + { 8, new List { 8 } }, + { 9, new List { 9 } }, + { 10, new List { 10 } }, + { 11, new List { 11 } }, + { 12, new List { 12 } }, + { 15, new List { 15 } }, + { 16, new List { 16 } } + }; + + if (typeMapping.TryGetValue(type, out var types)) + { + query = query.Where(g => types.Contains(g.Type)); + } + else + { + // 默认类型: 2, 6, 8, 16 + var defaultTypes = new List { 2, 6, 8, 16 }; + query = query.Where(g => defaultTypes.Contains(g.Type)); + } + + return query; + } + + /// + /// 应用解锁金额过滤 + /// + private async Task> ApplyUnlockAmountFilterAsync( + IQueryable query, int userId) + { + if (userId == 0) + { + // 未登录用户只能看到无门槛商品 + return query.Where(g => g.UnlockAmount == 0); + } + + // 获取用户信息 + var user = await _dbContext.Users + .Where(u => u.Id == userId) + .Select(u => new { u.Id, u.IsTest }) + .FirstOrDefaultAsync(); + + if (user == null) + { + return query.Where(g => g.UnlockAmount == 0); + } + + // 计算用户消费金额 + decimal orderMoney; + if (user.IsTest > 0) + { + // 推广账号使用折扣后金额 + orderMoney = await _dbContext.Orders + .Where(o => o.Status == 1 && o.UserId == userId) + .SumAsync(o => o.OrderZheTotal); + } + else + { + // 普通用户使用实际支付金额 + 余额抵扣 + var priceSum = await _dbContext.Orders + .Where(o => o.Status == 1 && o.UserId == userId) + .SumAsync(o => o.Price); + var useMoneySum = await _dbContext.Orders + .Where(o => o.Status == 1 && o.UserId == userId) + .SumAsync(o => o.UseMoney); + orderMoney = priceSum + useMoneySum; + } + + return query.Where(g => g.UnlockAmount <= orderMoney); + } + + /// + /// 获取商品类型映射 + /// + private async Task> GetGoodsTypesMapAsync() + { + var types = await _dbContext.GoodsTypes + .Select(t => new { t.Value, t.CornerText }) + .ToListAsync(); + + return types + .Where(t => !string.IsNullOrEmpty(t.CornerText)) + .ToDictionary(t => t.Value, t => t.CornerText!); + } + + /// + /// 获取商品参与次数 + /// + private async Task GetJoinCountAsync(int goodsId, int num, int orderType) + { + // 先从缓存获取 + var joinCount = await _cacheService.GetJoinCountAsync(goodsId); + if (joinCount >= 0) + { + return joinCount; + } + + // 缓存未命中,从数据库查询 + joinCount = await _dbContext.OrderItems + .Where(oi => oi.GoodsId == goodsId + && oi.Num == num + && oi.ShangId >= ShangCountIdRange[0] + && oi.ShangId <= ShangCountIdRange[1] + && oi.OrderType == orderType) + .CountAsync(); + + // 设置缓存 + await _cacheService.SetJoinCountAsync(goodsId, joinCount); + + return joinCount; + } + + /// + /// 格式化图片URL + /// + private static string FormatImageUrl(string? imgUrl) + { + if (string.IsNullOrEmpty(imgUrl)) + { + return string.Empty; + } + + // 如果已经是完整URL,直接返回 + if (imgUrl.StartsWith("http://") || imgUrl.StartsWith("https://")) + { + return imgUrl; + } + + // 否则添加基础URL (这里可以从配置读取) + return imgUrl; + } + + /// + public async Task GetGoodsDetailAsync(int goodsId, int goodsNum, int userId) + { + // 1. 查询商品基本信息 + var goods = await _dbContext.Goods + .Where(g => g.Id == goodsId) + .Select(g => new + { + g.Id, + g.Title, + g.ImgUrlDetail, + g.Price, + g.Stock, + g.SaleStock, + g.LockIs, + g.Type, + g.Status, + g.SaleTime, + g.IsShouZhe, + g.QuanjuXiangou, + g.DailyXiangou, + g.CouponIs, + g.CouponPro, + g.IntegralIs, + g.RageIs, + g.Rage, + g.LingzhuIs, + g.LingzhuFan, + g.CardNum, + g.NewIs, + g.GoodsDescribe + }) + .FirstOrDefaultAsync(); + + if (goods == null) + { + throw new InvalidOperationException("盒子不存在"); + } + + if (goods.Status != 1 && goods.Status != 3) + { + throw new InvalidOperationException("盒子已下架"); + } + + if (goodsNum > goods.Stock || goodsNum < 0) + { + throw new InvalidOperationException("箱号错误"); + } + + // 2. 自动选择箱号 (当goodsNum=0时) + if (goodsNum == 0) + { + var availableBox = await _dbContext.GoodsItems + .Where(gi => gi.GoodsId == goodsId + && gi.ShangId > 5 + && gi.GoodsListId == 0) + .GroupBy(gi => gi.Num) + .Select(g => new + { + Num = g.Key, + AllSurplusStock = g.Sum(x => x.SurplusStock) + }) + .Where(x => x.AllSurplusStock > 0) + .OrderBy(x => x.Num) + .FirstOrDefaultAsync(); + + goodsNum = availableBox?.Num ?? 1; + } + + // 3. 获取商品类型文字 + var goodsType = await _dbContext.GoodsTypes + .Where(t => t.Value == goods.Type) + .Select(t => t.CornerText) + .FirstOrDefaultAsync(); + + // 4. 获取本箱子余量 + var goodsSurplus = await _dbContext.GoodsItems + .Where(gi => gi.GoodsId == goodsId + && gi.Num == goodsNum + && gi.GoodsListId == 0 + && gi.ShangId >= ShangPrizeIdRange[0] + && gi.ShangId <= ShangPrizeIdRange[1]) + .GroupBy(gi => 1) + .Select(g => new + { + Stock = g.Sum(x => x.Stock), + SurplusStock = g.Sum(x => x.SurplusStock) + }) + .FirstOrDefaultAsync(); + + var allSurplusStock = goodsSurplus?.SurplusStock ?? 0; + + // 5. 获取所有奖品信息 + var goodsList = await GetGoodsListWithProbabilityAsync(goodsId, goodsNum, allSurplusStock); + + // 6. 获取锁箱信息 + var lockInfo = await GetLockInfoAsync(goodsId, goodsNum, goods.LockIs); + + // 7. 获取参与用户头像列表 + var joinUsers = await GetJoinUsersAsync(goodsId, goodsNum, goods.Type); + + // 8. 获取参与次数 + var joinCount = await GetJoinCountAsync(goodsId, goodsNum, goods.Type); + + // 9. 获取时间配置 + var (threeTime, fiveTime) = await GetTimeConfigAsync(); + + // 10. 获取限购信息 + var limitInfo = await GetLimitInfoAsync(userId, goodsId, goodsNum, goods.Type, goods.DailyXiangou, goods.QuanjuXiangou); + + // 11. 获取收藏状态 + var collectionIs = 0; + if (userId > 0) + { + collectionIs = await _dbContext.GoodsCollections + .AnyAsync(c => c.UserId == userId && c.GoodsId == goodsId && c.Num == goodsNum) ? 1 : 0; + } + + // 12. 构建响应 + var response = new GoodsDetailResponseDto + { + Goods = new GoodsInfoDto + { + Id = goods.Id, + Title = goods.Title, + ImgUrlDetail = FormatImageUrl(goods.ImgUrlDetail), + Price = goods.Price.ToString("0.##"), + Stock = goods.Stock, + SaleStock = goods.SaleStock, + SurplusStock = goods.Stock - goods.SaleStock, + GoodslistStock = goodsSurplus?.Stock ?? 0, + GoodslistSurplusStock = goodsSurplus?.SurplusStock ?? 0, + Type = goods.Type, + TypeText = goodsType, + Status = goods.Status, + LockIs = goods.LockIs, + Num = goodsNum, + Addtime = goods.SaleTime.HasValue ? goods.SaleTime.Value.ToString("MM-dd") : "", + GoodsDescribe = goods.GoodsDescribe, + CouponIs = goods.CouponIs, + CouponPro = goods.CouponPro, + IntegralIs = goods.IntegralIs, + RageIs = goods.RageIs, + Rage = goods.Rage, + LingzhuIs = goods.LingzhuIs, + LingzhuFan = goods.LingzhuFan, + CardNum = goods.CardNum, + IsShouZhe = goods.IsShouZhe, + NewIs = goods.NewIs, + CollectionIs = collectionIs, + ThreeTime = threeTime, + FiveTime = fiveTime, + QuanjuXiangou = goods.QuanjuXiangou, + DailyXiangou = goods.DailyXiangou + }, + LockInfo = lockInfo, + JoinUser = joinUsers, + JoinCount = joinCount, + GoodsList = goodsList, + LimitInfo = limitInfo + }; + + return response; + } + + /// + /// 获取奖品列表并计算概率 + /// + private async Task> GetGoodsListWithProbabilityAsync(int goodsId, int goodsNum, int allSurplusStock) + { + // 获取所有奖品信息 + var goodsItems = await _dbContext.GoodsItems + .Where(gi => gi.GoodsId == goodsId + && gi.Num == goodsNum + && gi.GoodsListId == 0) + .OrderByDescending(gi => gi.Sort) + .ThenBy(gi => gi.ShangId) + .ThenBy(gi => gi.Id) + .Select(gi => new + { + gi.Id, + gi.ShangId, + gi.Title, + gi.Stock, + gi.SurplusStock, + gi.ImgUrl, + gi.GoodsType, + gi.SaleTime, + gi.Price, + gi.ScMoney + }) + .ToListAsync(); + + // 获取所有赏品等级信息 + var shangIds = goodsItems.Where(x => x.ShangId.HasValue).Select(x => x.ShangId!.Value).Distinct().ToList(); + var shangInfos = await _dbContext.PrizeLevels + .Where(p => shangIds.Contains(p.Id)) + .ToDictionaryAsync(p => p.Id, p => new ShangInfoDto + { + Id = p.Id, + Title = p.Title, + ImgUrl = FormatImageUrl(p.ImgUrl), + Color = p.Color, + SpecialImgUrl = FormatImageUrl(p.SpecialImgUrl) + }); + + // 计算概率 + var result = new List(); + decimal proAll = 0; + decimal proMax = 0; + int proMaxIndex = 0; + + for (int i = 0; i < goodsItems.Count; i++) + { + var item = goodsItems[i]; + var shangId = item.ShangId ?? 0; + var surplusStock = item.SurplusStock; + + string pro; + decimal proNum = 0; + + // 赠送赏品不计入概率 + if (ShangGiveIds.Contains(shangId)) + { + pro = ShangGiveText; + } + else + { + if (surplusStock > 0 && allSurplusStock > 0) + { + proNum = Math.Round((decimal)surplusStock / allSurplusStock * 100, 2); + pro = $"概率:{RemoveTrailingZeros(proNum)}%"; + } + else + { + pro = "概率:0%"; + } + } + + // 计算剩余概率 + proAll += proNum; + if (proNum >= proMax) + { + proMax = proNum; + proMaxIndex = i; + } + + var dto = new GoodsListItemDto + { + Id = item.Id, + ShangId = shangId, + ShangInfo = shangId > 0 && shangInfos.TryGetValue(shangId, out var info) ? info : null, + Title = item.Title, + Stock = item.Stock, + SurplusStock = surplusStock, + ImgUrl = FormatImageUrl(item.ImgUrl), + GoodsType = item.GoodsType, + Price = (item.Price * 1).ToString("0.##"), + ScMoney = item.ScMoney.ToString("0.##"), + SaleTime = item.SaleTime.HasValue ? item.SaleTime.Value.ToString("yyyy-MM-dd") : null, + Pro = pro, + Children = item.GoodsType == 4 // goods_type=4 表示有子奖品 + }; + + result.Add(dto); + } + + // 概率不足100%时,补足到最大概率的奖品 + if (proMax > 0 && proAll < 100 && result.Count > proMaxIndex) + { + var surplusPro = 100 - proAll; + var newPro = proMax + surplusPro; + result[proMaxIndex].Pro = $"概率:{RemoveTrailingZeros(newPro)}%"; + } + + return result; + } + + /// + /// 获取锁箱信息 + /// + private async Task GetLockInfoAsync(int goodsId, int goodsNum, int lockIs) + { + var lockInfo = new LockInfoDto + { + LockIs = lockIs, + GoodsLockUserNickname = null, + GoodsLockUserHeadimg = null, + GoodsLockSurplusTime = 0 + }; + + if (lockIs == 0) + { + return lockInfo; + } + + var goodsIdNum = $"{goodsId}_{goodsNum}"; + var currentTime = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); + + var goodsLock = await _dbContext.GoodsLocks + .Where(gl => gl.GoodsIdNum == goodsIdNum && gl.EndTime > currentTime) + .OrderByDescending(gl => gl.Id) + .FirstOrDefaultAsync(); + + if (goodsLock != null) + { + lockInfo.GoodsLockSurplusTime = goodsLock.EndTime; + + var lockUser = await _dbContext.Users + .Where(u => u.Id == goodsLock.UserId) + .Select(u => new { u.Nickname, u.HeadImg }) + .FirstOrDefaultAsync(); + + if (lockUser != null) + { + lockInfo.GoodsLockUserNickname = lockUser.Nickname; + lockInfo.GoodsLockUserHeadimg = FormatImageUrl(lockUser.HeadImg); + } + } + + return lockInfo; + } + + /// + /// 获取参与用户头像列表 + /// + private async Task> GetJoinUsersAsync(int goodsId, int goodsNum, int orderType) + { + var userIds = await _dbContext.OrderItems + .Where(oi => oi.GoodsId == goodsId + && oi.Num == goodsNum + && oi.ShangId >= ShangCountIdRange[0] + && oi.ShangId <= ShangCountIdRange[1] + && oi.OrderType == orderType) + .OrderByDescending(oi => oi.Id) + .Select(oi => oi.UserId) + .Distinct() + .Take(5) + .ToListAsync(); + + if (!userIds.Any()) + { + return new List(); + } + + var users = await _dbContext.Users + .Where(u => userIds.Contains(u.Id)) + .Select(u => new { u.Id, u.HeadImg }) + .ToListAsync(); + + // 保持原始顺序 + return userIds + .Select(id => users.FirstOrDefault(u => u.Id == id)?.HeadImg) + .Where(img => !string.IsNullOrEmpty(img)) + .Select(img => FormatImageUrl(img!)) + .ToList(); + } + + /// + /// 获取时间配置 + /// + private async Task<(int ThreeTime, int FiveTime)> GetTimeConfigAsync() + { + var config = await _dbContext.Configs + .Where(c => c.ConfigKey == "base") + .Select(c => c.ConfigValue) + .FirstOrDefaultAsync(); + + if (string.IsNullOrEmpty(config)) + { + return (0, 0); + } + + try + { + var configObj = System.Text.Json.JsonSerializer.Deserialize>(config); + var threeTime = 0; + var fiveTime = 0; + + if (configObj != null) + { + if (configObj.TryGetValue("three_time", out var threeTimeObj)) + { + int.TryParse(threeTimeObj?.ToString(), out threeTime); + } + if (configObj.TryGetValue("five_time", out var fiveTimeObj)) + { + int.TryParse(fiveTimeObj?.ToString(), out fiveTime); + } + } + + return (threeTime, fiveTime); + } + catch + { + return (0, 0); + } + } + + /// + /// 获取限购信息 + /// + private async Task GetLimitInfoAsync(int userId, int goodsId, int goodsNum, int orderType, int dailyXiangou, int quanjuXiangou) + { + var limitInfo = new LimitInfoDto + { + DailyXiangou = dailyXiangou, + DailyBought = 0, + DailyRemaining = dailyXiangou, + QuanjuXiangou = quanjuXiangou, + GlobalBought = 0, + GlobalRemaining = quanjuXiangou + }; + + if (userId <= 0) + { + return limitInfo; + } + + // 计算今日已购数量 + if (dailyXiangou > 0) + { + var todayStart = DateTime.Today; + var todayEnd = todayStart.AddDays(1); + + var dailyBought = await _dbContext.OrderItems + .Where(oi => oi.GoodsId == goodsId + && oi.UserId == userId + && oi.Num == goodsNum + && oi.ShangId >= ShangCountIdRange[0] + && oi.ShangId <= ShangCountIdRange[1] + && oi.OrderType == orderType + && oi.CreatedAt >= todayStart + && oi.CreatedAt < todayEnd) + .CountAsync(); + + limitInfo.DailyBought = dailyBought; + limitInfo.DailyRemaining = Math.Max(0, dailyXiangou - dailyBought); + } + + // 计算全局已购数量 + if (quanjuXiangou > 0) + { + var globalBought = await _dbContext.OrderItems + .Where(oi => oi.GoodsId == goodsId + && oi.UserId == userId + && oi.Num == goodsNum + && oi.ShangId >= ShangCountIdRange[0] + && oi.ShangId <= ShangCountIdRange[1] + && oi.OrderType == orderType) + .CountAsync(); + + limitInfo.GlobalBought = globalBought; + limitInfo.GlobalRemaining = Math.Max(0, quanjuXiangou - globalBought); + } + + return limitInfo; + } + + /// + /// 移除小数末尾的零 + /// + private static string RemoveTrailingZeros(decimal value) + { + return value.ToString("0.##"); + } + + // 赠送赏品文字 + private const string ShangGiveText = "只赠不售"; + + /// + public async Task> GetGoodsChildrenAsync(int goodsId, int goodsNum, int goodsListId) + { + // 1. 验证参数 + if (goodsListId <= 0) + { + throw new InvalidOperationException("对应的宝箱ID不能为空"); + } + + // 2. 验证商品存在且状态有效 + var goods = await _dbContext.Goods + .Where(g => g.Id == goodsId) + .Select(g => new { g.Id, g.Title, g.Status }) + .FirstOrDefaultAsync(); + + if (goods == null) + { + throw new InvalidOperationException("盒子不存在"); + } + + if (goods.Status != 1 && goods.Status != 3) + { + throw new InvalidOperationException("盒子已下架"); + } + + // 3. 查询所有子奖品信息 + var childItems = await _dbContext.GoodsItems + .Where(gi => gi.GoodsId == goodsId + && gi.Num == goodsNum + && gi.GoodsListId == goodsListId) + .OrderByDescending(gi => gi.Sort) + .ThenBy(gi => gi.ShangId) + .ThenBy(gi => gi.Id) + .Select(gi => new + { + gi.Id, + gi.ShangId, + gi.Title, + gi.Stock, + gi.SurplusStock, + gi.ImgUrl, + gi.GoodsType, + gi.SaleTime, + gi.Price, + gi.RealPro, + gi.ScMoney + }) + .ToListAsync(); + + if (!childItems.Any()) + { + return new List(); + } + + // 4. 获取所有赏品等级信息 + var shangIds = childItems + .Where(x => x.ShangId.HasValue) + .Select(x => x.ShangId!.Value) + .Distinct() + .ToList(); + + var shangInfos = await _dbContext.PrizeLevels + .Where(p => shangIds.Contains(p.Id)) + .ToDictionaryAsync(p => p.Id, p => new ShangInfoDto + { + Id = p.Id, + Title = p.Title, + ImgUrl = FormatImageUrl(p.ImgUrl), + Color = p.Color, + SpecialImgUrl = FormatImageUrl(p.SpecialImgUrl) + }); + + // 5. 计算总剩余库存 (排除赠送赏品) + var totalSurplusStock = childItems + .Where(x => x.ShangId.HasValue && !ShangGiveIds.Contains(x.ShangId.Value)) + .Sum(x => x.SurplusStock); + + // 6. 构建结果列表并计算概率 + var result = new List(); + + foreach (var item in childItems) + { + var shangId = item.ShangId ?? 0; + var dto = new GoodsChildrenDto + { + Id = item.Id, + ShangId = shangId, + Title = item.Title, + ImgUrl = FormatImageUrl(item.ImgUrl), + GoodsType = item.GoodsType, + SaleTime = item.SaleTime.HasValue ? item.SaleTime.Value.ToString("yyyy-MM-dd") : null, + Price = (item.Price * 1).ToString("0.##"), + ScMoney = item.ScMoney.ToString("0.##") + }; + + // 设置shang_info (shang_id=38时不返回) + if (shangId > 0 && shangId != 38 && shangInfos.TryGetValue(shangId, out var info)) + { + dto.ShangInfo = info; + } + + // 计算概率 + string pro; + decimal proNum = 0; + + if (item.Stock == 0) + { + // 库存为0时使用数据库中的real_pro + proNum = Math.Round(item.RealPro, 2); + pro = $"概率:{RemoveTrailingZeros(proNum)}%"; + dto.RealPro = RemoveTrailingZeros(proNum); + dto.ProNum = RemoveTrailingZeros(proNum); + // 库存为0时不返回stock和surplus_stock (保持默认值0,会被JsonIgnore) + } + else if (ShangGiveIds.Contains(shangId)) + { + // 赠送赏品显示"只赠不售" + pro = ShangGiveText; + dto.RealPro = "0"; + dto.ProNum = "0"; + dto.Stock = item.Stock; + dto.SurplusStock = item.SurplusStock; + } + else + { + // 正常计算概率 + if (item.SurplusStock > 0 && totalSurplusStock > 0) + { + proNum = Math.Round((decimal)item.SurplusStock / totalSurplusStock * 100, 2); + pro = $"概率:{RemoveTrailingZeros(proNum)}%"; + } + else + { + pro = "概率:0%"; + } + dto.RealPro = RemoveTrailingZeros(proNum); + dto.ProNum = RemoveTrailingZeros(proNum); + dto.Stock = item.Stock; + dto.SurplusStock = item.SurplusStock; + } + + dto.Pro = pro; + result.Add(dto); + } + + return result; + } + + /// + public async Task GetGoodsExtendAsync(int goodsId, int goodsType) + { + // 1. 先尝试从goods_extensions表获取商品特定配置 + var goodsExtend = await _dbContext.GoodsExtensions + .Where(ge => ge.GoodsId == goodsId) + .Select(ge => new GoodsExtendDto + { + PayWechat = ge.PayWechat, + PayBalance = ge.PayBalance, + PayCurrency = ge.PayCurrency, + PayCurrency2 = ge.PayCurrency2, + PayCoupon = ge.PayCoupon, + IsDeduction = ge.IsDeduction + }) + .FirstOrDefaultAsync(); + + if (goodsExtend != null) + { + return goodsExtend; + } + + // 2. 如果没有商品特定配置,从goods_types表获取类型默认配置 + var typeConfig = await _dbContext.GoodsTypes + .Where(gt => gt.Value == goodsType) + .Select(gt => new GoodsExtendDto + { + PayWechat = gt.PayWechat, + PayBalance = gt.PayBalance, + PayCurrency = gt.PayCurrency, + PayCurrency2 = gt.PayCurrency2, + PayCoupon = gt.PayCoupon, + IsDeduction = gt.IsDeduction + }) + .FirstOrDefaultAsync(); + + if (typeConfig != null) + { + return typeConfig; + } + + // 3. 如果都没有找到,返回默认值(全部启用) + return new GoodsExtendDto + { + PayWechat = 1, + PayBalance = 1, + PayCurrency = 1, + PayCurrency2 = 1, + PayCoupon = 1, + IsDeduction = 1 + }; + } + + /// + public async Task> GetBoxListAsync(int goodsId) + { + // 1. 验证商品存在且状态有效 + var goods = await _dbContext.Goods + .Where(g => g.Id == goodsId) + .Select(g => new { g.Id, g.Stock, g.Status }) + .FirstOrDefaultAsync(); + + if (goods == null) + { + throw new InvalidOperationException("盒子不存在"); + } + + if (goods.Status != 1 && goods.Status != 3) + { + throw new InvalidOperationException("盒子已下架"); + } + + // 2. 计算分组(每10个一组) + const int pageSize = 10; + var totalBoxes = goods.Stock; + var groupCount = (int)Math.Ceiling((double)totalBoxes / pageSize); + + var result = new List(); + for (int i = 0; i < groupCount; i++) + { + var startNum = (i * pageSize) + 1; + var endNum = Math.Min((i * pageSize) + pageSize, totalBoxes); + + result.Add(new BoxGroupDto + { + Title = $"{startNum}-{endNum}", + PageNo = i + }); + } + + return result; + } + + /// + public async Task> GetBoxDetailAsync(int goodsId, int pageNo, int sort) + { + // 1. 验证商品存在且状态有效 + var goods = await _dbContext.Goods + .Where(g => g.Id == goodsId) + .Select(g => new { g.Id, g.Stock, g.Status }) + .FirstOrDefaultAsync(); + + if (goods == null) + { + throw new InvalidOperationException("盒子不存在"); + } + + if (goods.Status != 1 && goods.Status != 3) + { + throw new InvalidOperationException("盒子已下架"); + } + + // 2. 计算起始和结束箱号 + const int pageSize = 10; + if (pageNo < 0) pageNo = 0; + + var startNum = (pageNo * pageSize) + 1; + var endNum = Math.Min((pageNo * pageSize) + pageSize, goods.Stock); + + // 3. 获取所有赏品等级信息(用于shang_info) + var shangInfos = await _dbContext.PrizeLevels + .ToDictionaryAsync(p => p.Id, p => new ShangInfoDto + { + Id = p.Id, + Title = p.Title, + ImgUrl = FormatImageUrl(p.ImgUrl), + Color = p.Color, + SpecialImgUrl = FormatImageUrl(p.SpecialImgUrl) + }); + + // 4. 批量查询所有箱号的奖品信息 + var allGoodsItems = await _dbContext.GoodsItems + .Where(gi => gi.GoodsId == goodsId + && gi.Num >= startNum + && gi.Num <= endNum) + .OrderByDescending(gi => gi.Sort) + .ThenBy(gi => gi.ShangId) + .ThenBy(gi => gi.Id) + .Select(gi => new + { + gi.Id, + gi.Num, + gi.ShangId, + gi.Stock, + gi.SurplusStock + }) + .ToListAsync(); + + // 5. 按箱号分组处理 + var result = new List(); + for (int boxNum = startNum; boxNum <= endNum; boxNum++) + { + var boxItems = allGoodsItems.Where(gi => gi.Num == boxNum).ToList(); + + // 计算剩余库存(排除赠送赏品) + var surplusAllStock = boxItems + .Where(gi => gi.ShangId.HasValue && !ShangGiveIds.Contains(gi.ShangId.Value)) + .Sum(gi => gi.SurplusStock); + + if (surplusAllStock < 0) surplusAllStock = 0; + + // 构建奖品列表 + var goodsList = boxItems.Select(gi => + { + var shangId = gi.ShangId ?? 0; + return new BoxGoodsListDto + { + Id = gi.Id, + ShangId = shangId, + ShangInfo = shangId > 0 && shangInfos.TryGetValue(shangId, out var info) ? info : null, + Stock = gi.Stock, + SurplusStock = gi.SurplusStock + }; + }).ToList(); + + result.Add(new BoxDetailDto + { + Num = boxNum, + SurplusAllStock = surplusAllStock, + GoodsList = goodsList + }); + } + + // 6. 应用排序 + // sort: 0=箱号升序(默认), 1=箱号降序, 2=余量降序 + result = sort switch + { + 1 => result.OrderByDescending(x => x.Num).ToList(), + 2 => result.OrderByDescending(x => x.SurplusAllStock).ToList(), + _ => result.OrderBy(x => x.Num).ToList() + }; + + return result; + } +} diff --git a/server/C#/HoneyBox/src/HoneyBox.Core/Services/PrizeService.cs b/server/C#/HoneyBox/src/HoneyBox.Core/Services/PrizeService.cs new file mode 100644 index 00000000..17fe53e1 --- /dev/null +++ b/server/C#/HoneyBox/src/HoneyBox.Core/Services/PrizeService.cs @@ -0,0 +1,421 @@ +using HoneyBox.Core.Interfaces; +using HoneyBox.Model.Data; +using HoneyBox.Model.Models.Goods; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; + +namespace HoneyBox.Core.Services; + +/// +/// 奖品服务实现 +/// +public class PrizeService : IPrizeService +{ + private readonly HoneyBoxDbContext _dbContext; + private readonly ILogger _logger; + + // 抽奖赏品ID范围 [10, 33] + private static readonly int[] ShangPrizeIdRange = { 10, 33 }; + // 赠送赏品ID (不计入统计) + private static readonly int[] ShangGiveIds = { 1, 2, 3, 4, 5 }; + + public PrizeService( + HoneyBoxDbContext dbContext, + ILogger logger) + { + _dbContext = dbContext; + _logger = logger; + } + + /// + public async Task GetPrizeCountAsync(int goodsId) + { + // 1. 验证商品存在且状态有效 + var goods = await _dbContext.Goods + .Where(g => g.Id == goodsId) + .Select(g => new { g.Id, g.Status }) + .FirstOrDefaultAsync(); + + if (goods == null) + { + throw new InvalidOperationException("盒子不存在"); + } + + if (goods.Status != 1 && goods.Status != 3) + { + throw new InvalidOperationException("盒子已下架"); + } + + // 2. 按赏品分类统计数量 + var prizeStats = await _dbContext.GoodsItems + .Where(gi => gi.GoodsId == goodsId + && gi.GoodsListId == 0 + && gi.ShangId.HasValue + && !ShangGiveIds.Contains(gi.ShangId.Value)) + .GroupBy(gi => gi.ShangId) + .Select(g => new + { + ShangId = g.Key ?? 0, + Total = g.Sum(x => x.Stock), + Surplus = g.Sum(x => x.SurplusStock) + }) + .OrderBy(x => x.ShangId) + .ToListAsync(); + + // 3. 获取所有赏品等级信息 + var shangIds = prizeStats.Select(x => x.ShangId).Distinct().ToList(); + var shangInfos = await _dbContext.PrizeLevels + .Where(p => shangIds.Contains(p.Id)) + .ToDictionaryAsync(p => p.Id, p => new ShangInfoDto + { + Id = p.Id, + Title = p.Title, + ImgUrl = FormatImageUrl(p.ImgUrl), + Color = p.Color, + SpecialImgUrl = FormatImageUrl(p.SpecialImgUrl) + }); + + // 4. 构建结果 + var list = prizeStats.Select(ps => new PrizeCountItemDto + { + ShangId = ps.ShangId, + ShangInfo = shangInfos.TryGetValue(ps.ShangId, out var info) ? info : null, + Total = ps.Total, + Surplus = ps.Surplus + }).ToList(); + + return new PrizeCountResponseDto + { + List = list, + Total = list.Sum(x => x.Total), + Surplus = list.Sum(x => x.Surplus) + }; + } + + /// + public async Task GetPrizeContentAsync(int goodsId, int num) + { + // 1. 验证商品存在且状态有效 + var goods = await _dbContext.Goods + .Where(g => g.Id == goodsId) + .Select(g => new { g.Id, g.Status, g.Stock }) + .FirstOrDefaultAsync(); + + if (goods == null) + { + throw new InvalidOperationException("盒子不存在"); + } + + if (goods.Status != 1 && goods.Status != 3) + { + throw new InvalidOperationException("盒子已下架"); + } + + // 验证箱号有效性 + if (num < 0 || num > goods.Stock) + { + throw new InvalidOperationException("箱号错误"); + } + + // 如果num为0,默认查询第1箱 + if (num == 0) + { + num = 1; + } + + // 2. 获取本箱子总剩余库存(用于计算概率) + var allSurplusStock = await _dbContext.GoodsItems + .Where(gi => gi.GoodsId == goodsId + && gi.Num == num + && gi.GoodsListId == 0 + && gi.ShangId.HasValue + && !ShangGiveIds.Contains(gi.ShangId.Value)) + .SumAsync(gi => gi.SurplusStock); + + // 3. 查询指定箱号的奖品列表 + var goodsItems = await _dbContext.GoodsItems + .Where(gi => gi.GoodsId == goodsId + && gi.Num == num + && gi.GoodsListId == 0) + .OrderByDescending(gi => gi.Sort) + .ThenBy(gi => gi.ShangId) + .ThenBy(gi => gi.Id) + .Select(gi => new + { + gi.Id, + gi.ShangId, + gi.Title, + gi.Stock, + gi.SurplusStock, + gi.ImgUrl, + gi.GoodsType, + gi.SaleTime, + gi.Price, + gi.ScMoney + }) + .ToListAsync(); + + // 4. 获取所有赏品等级信息 + var shangIds = goodsItems + .Where(x => x.ShangId.HasValue) + .Select(x => x.ShangId!.Value) + .Distinct() + .ToList(); + + var shangInfos = await _dbContext.PrizeLevels + .Where(p => shangIds.Contains(p.Id)) + .ToDictionaryAsync(p => p.Id, p => new ShangInfoDto + { + Id = p.Id, + Title = p.Title, + ImgUrl = FormatImageUrl(p.ImgUrl), + Color = p.Color, + SpecialImgUrl = FormatImageUrl(p.SpecialImgUrl) + }); + + // 5. 构建结果列表并计算概率 + var result = new List(); + decimal proAll = 0; + decimal proMax = 0; + int proMaxIndex = 0; + + for (int i = 0; i < goodsItems.Count; i++) + { + var item = goodsItems[i]; + var shangId = item.ShangId ?? 0; + var surplusStock = item.SurplusStock; + + string pro; + decimal proNum = 0; + + // 赠送赏品不计入概率 + if (ShangGiveIds.Contains(shangId)) + { + pro = "只赠不售"; + } + else + { + if (surplusStock > 0 && allSurplusStock > 0) + { + proNum = Math.Round((decimal)surplusStock / allSurplusStock * 100, 2); + pro = $"概率:{RemoveTrailingZeros(proNum)}%"; + } + else + { + pro = "概率:0%"; + } + } + + // 计算剩余概率 + proAll += proNum; + if (proNum >= proMax) + { + proMax = proNum; + proMaxIndex = i; + } + + var dto = new GoodsListItemDto + { + Id = item.Id, + ShangId = shangId, + ShangInfo = shangId > 0 && shangInfos.TryGetValue(shangId, out var info) ? info : null, + Title = item.Title, + Stock = item.Stock, + SurplusStock = surplusStock, + ImgUrl = FormatImageUrl(item.ImgUrl), + GoodsType = item.GoodsType, + Price = (item.Price * 1).ToString("0.##"), + ScMoney = item.ScMoney.ToString("0.##"), + SaleTime = item.SaleTime.HasValue ? item.SaleTime.Value.ToString("yyyy-MM-dd") : null, + Pro = pro, + Children = item.GoodsType == 4 // goods_type=4 表示有子奖品 + }; + + result.Add(dto); + } + + // 概率不足100%时,补足到最大概率的奖品 + if (proMax > 0 && proAll < 100 && result.Count > proMaxIndex) + { + var surplusPro = 100 - proAll; + var newPro = proMax + surplusPro; + result[proMaxIndex].Pro = $"概率:{RemoveTrailingZeros(newPro)}%"; + } + + return new PrizeContentResponseDto + { + List = result + }; + } + + /// + public async Task GetPrizeLogsAsync(int goodsId, int goodsNum, int shangId, int page, int pageSize) + { + // 1. 验证商品存在且状态有效 + var goods = await _dbContext.Goods + .Where(g => g.Id == goodsId) + .Select(g => new { g.Id, g.Status, g.Type, g.Stock }) + .FirstOrDefaultAsync(); + + if (goods == null) + { + throw new InvalidOperationException("盒子不存在"); + } + + if (goods.Status != 1 && goods.Status != 3) + { + throw new InvalidOperationException("盒子已下架"); + } + + // 验证箱号有效性 + if (goodsNum < 0) + { + throw new InvalidOperationException("箱号选择错误"); + } + + // 2. 获取中奖记录分类列表 (按shang_id分组) + var categoryList = await _dbContext.GoodsItems + .Where(gi => gi.GoodsId == goodsId && gi.Num == goodsNum) + .Select(gi => gi.ShangId) + .Distinct() + .ToListAsync(); + + // 获取赏品等级信息 + var shangIds = categoryList.Where(s => s.HasValue).Select(s => s!.Value).ToList(); + var shangInfos = await _dbContext.PrizeLevels + .Where(p => shangIds.Contains(p.Id)) + .ToDictionaryAsync(p => p.Id, p => new { p.Title, p.Color }); + + // 构建分类列表,添加"全部"选项 + var categories = new List + { + new CategoryDto { ShangId = 0, ShangTitle = "全部" } + }; + + foreach (var sid in categoryList.Where(s => s.HasValue).Select(s => s!.Value).OrderBy(s => s)) + { + if (shangInfos.TryGetValue(sid, out var info)) + { + categories.Add(new CategoryDto + { + ShangId = sid, + ShangTitle = info.Title + }); + } + } + + // 3. 构建中奖记录查询 + var query = _dbContext.OrderItems + .Where(oi => oi.GoodsId == goodsId + && oi.Num == goodsNum + && oi.OrderType == goods.Type + && oi.Source == 1); + + // 如果指定了shang_id,添加筛选条件 + if (shangId > 0) + { + query = query.Where(oi => oi.ShangId == shangId); + } + + // 4. 按order_id, goodslist_id, user_id分组,统计prize_num + var groupedQuery = query + .GroupBy(oi => new { oi.OrderId, oi.GoodslistId, oi.UserId }) + .Select(g => new + { + UserId = g.Key.UserId, + GoodslistTitle = g.First().GoodslistTitle, + GoodslistImgurl = g.First().GoodslistImgurl, + ShangId = g.First().ShangId, + Addtime = g.First().Addtime, + PrizeNum = g.Count() + }) + .OrderByDescending(x => x.Addtime); + + // 5. 计算总数和分页 + var totalCount = await query + .GroupBy(oi => new { oi.OrderId, oi.GoodslistId, oi.UserId }) + .CountAsync(); + + var totalPages = (int)Math.Ceiling((double)totalCount / pageSize); + + // 6. 获取分页数据 + var prizeLogData = await groupedQuery + .Skip((page - 1) * pageSize) + .Take(pageSize) + .ToListAsync(); + + // 7. 获取用户信息 + var userIds = prizeLogData.Select(x => x.UserId).Distinct().ToList(); + var users = await _dbContext.Users + .Where(u => userIds.Contains(u.Id)) + .ToDictionaryAsync(u => u.Id, u => new { u.Nickname, u.HeadImg }); + + // 8. 获取赏品颜色信息 + var prizeShangIds = prizeLogData.Select(x => x.ShangId).Distinct().ToList(); + var prizeShangInfos = await _dbContext.PrizeLevels + .Where(p => prizeShangIds.Contains(p.Id)) + .ToDictionaryAsync(p => p.Id, p => new { p.Title, p.Color }); + + // 9. 构建结果 + var data = prizeLogData.Select(item => + { + var userInfo = users.TryGetValue(item.UserId, out var user) + ? new PrizeLogUserDto + { + Nickname = user.Nickname, + HeadImg = FormatImageUrl(user.HeadImg) + } + : null; + + var shangInfo = prizeShangInfos.TryGetValue(item.ShangId, out var shang) ? shang : null; + + return new PrizeLogDto + { + UserId = item.UserId, + GoodslistTitle = item.GoodslistTitle ?? string.Empty, + GoodslistImgurl = FormatImageUrl(item.GoodslistImgurl), + ShangId = item.ShangId, + Addtime = DateTimeOffset.FromUnixTimeSeconds(item.Addtime).LocalDateTime.ToString("yyyy-MM-dd HH:mm:ss"), + PrizeNum = item.PrizeNum, + ShangTitle = shangInfo?.Title ?? string.Empty, + UserInfo = userInfo, + ShangColor = shangInfo?.Color ?? string.Empty + }; + }).ToList(); + + return new PrizeLogsResponseDto + { + Category = categories, + Data = data, + LastPage = totalPages, + Total = totalCount + }; + } + + /// + /// 格式化图片URL + /// + private static string FormatImageUrl(string? imgUrl) + { + if (string.IsNullOrEmpty(imgUrl)) + { + return string.Empty; + } + + // 如果已经是完整URL,直接返回 + if (imgUrl.StartsWith("http://") || imgUrl.StartsWith("https://")) + { + return imgUrl; + } + + return imgUrl; + } + + /// + /// 移除小数末尾的零 + /// + private static string RemoveTrailingZeros(decimal value) + { + return value.ToString("0.##"); + } +} diff --git a/server/C#/HoneyBox/src/HoneyBox.Core/bin/Debug/net10.0/HoneyBox.Core.dll b/server/C#/HoneyBox/src/HoneyBox.Core/bin/Debug/net10.0/HoneyBox.Core.dll index 82fbad46..eb1d495c 100644 Binary files a/server/C#/HoneyBox/src/HoneyBox.Core/bin/Debug/net10.0/HoneyBox.Core.dll and b/server/C#/HoneyBox/src/HoneyBox.Core/bin/Debug/net10.0/HoneyBox.Core.dll differ diff --git a/server/C#/HoneyBox/src/HoneyBox.Core/bin/Debug/net10.0/HoneyBox.Core.pdb b/server/C#/HoneyBox/src/HoneyBox.Core/bin/Debug/net10.0/HoneyBox.Core.pdb index f37d0d47..5ea93cf6 100644 Binary files a/server/C#/HoneyBox/src/HoneyBox.Core/bin/Debug/net10.0/HoneyBox.Core.pdb and b/server/C#/HoneyBox/src/HoneyBox.Core/bin/Debug/net10.0/HoneyBox.Core.pdb differ diff --git a/server/C#/HoneyBox/src/HoneyBox.Core/bin/Debug/net10.0/HoneyBox.Model.dll b/server/C#/HoneyBox/src/HoneyBox.Core/bin/Debug/net10.0/HoneyBox.Model.dll index e8943c95..1b4d5a5e 100644 Binary files a/server/C#/HoneyBox/src/HoneyBox.Core/bin/Debug/net10.0/HoneyBox.Model.dll and b/server/C#/HoneyBox/src/HoneyBox.Core/bin/Debug/net10.0/HoneyBox.Model.dll differ diff --git a/server/C#/HoneyBox/src/HoneyBox.Core/bin/Debug/net10.0/HoneyBox.Model.pdb b/server/C#/HoneyBox/src/HoneyBox.Core/bin/Debug/net10.0/HoneyBox.Model.pdb index eee28ec8..e48e680e 100644 Binary files a/server/C#/HoneyBox/src/HoneyBox.Core/bin/Debug/net10.0/HoneyBox.Model.pdb and b/server/C#/HoneyBox/src/HoneyBox.Core/bin/Debug/net10.0/HoneyBox.Model.pdb differ diff --git a/server/C#/HoneyBox/src/HoneyBox.Core/obj/Debug/net10.0/HoneyBox.Core.AssemblyInfo.cs b/server/C#/HoneyBox/src/HoneyBox.Core/obj/Debug/net10.0/HoneyBox.Core.AssemblyInfo.cs index ecdd7242..8dedb2af 100644 --- a/server/C#/HoneyBox/src/HoneyBox.Core/obj/Debug/net10.0/HoneyBox.Core.AssemblyInfo.cs +++ b/server/C#/HoneyBox/src/HoneyBox.Core/obj/Debug/net10.0/HoneyBox.Core.AssemblyInfo.cs @@ -13,7 +13,7 @@ using System.Reflection; [assembly: System.Reflection.AssemblyCompanyAttribute("HoneyBox.Core")] [assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")] [assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")] -[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+21c4bb5c6a0c35c345420f3a29d64fd2922e7427")] +[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+7e00d28ad48928059f0ff514ed804f2cb0626442")] [assembly: System.Reflection.AssemblyProductAttribute("HoneyBox.Core")] [assembly: System.Reflection.AssemblyTitleAttribute("HoneyBox.Core")] [assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")] diff --git a/server/C#/HoneyBox/src/HoneyBox.Core/obj/Debug/net10.0/HoneyBox.Core.dll b/server/C#/HoneyBox/src/HoneyBox.Core/obj/Debug/net10.0/HoneyBox.Core.dll index 82fbad46..eb1d495c 100644 Binary files a/server/C#/HoneyBox/src/HoneyBox.Core/obj/Debug/net10.0/HoneyBox.Core.dll and b/server/C#/HoneyBox/src/HoneyBox.Core/obj/Debug/net10.0/HoneyBox.Core.dll differ diff --git a/server/C#/HoneyBox/src/HoneyBox.Core/obj/Debug/net10.0/HoneyBox.Core.pdb b/server/C#/HoneyBox/src/HoneyBox.Core/obj/Debug/net10.0/HoneyBox.Core.pdb index f37d0d47..5ea93cf6 100644 Binary files a/server/C#/HoneyBox/src/HoneyBox.Core/obj/Debug/net10.0/HoneyBox.Core.pdb and b/server/C#/HoneyBox/src/HoneyBox.Core/obj/Debug/net10.0/HoneyBox.Core.pdb differ diff --git a/server/C#/HoneyBox/src/HoneyBox.Core/obj/Debug/net10.0/ref/HoneyBox.Core.dll b/server/C#/HoneyBox/src/HoneyBox.Core/obj/Debug/net10.0/ref/HoneyBox.Core.dll index 5b047f0d..0e35f8c6 100644 Binary files a/server/C#/HoneyBox/src/HoneyBox.Core/obj/Debug/net10.0/ref/HoneyBox.Core.dll and b/server/C#/HoneyBox/src/HoneyBox.Core/obj/Debug/net10.0/ref/HoneyBox.Core.dll differ diff --git a/server/C#/HoneyBox/src/HoneyBox.Core/obj/Debug/net10.0/refint/HoneyBox.Core.dll b/server/C#/HoneyBox/src/HoneyBox.Core/obj/Debug/net10.0/refint/HoneyBox.Core.dll index 5b047f0d..0e35f8c6 100644 Binary files a/server/C#/HoneyBox/src/HoneyBox.Core/obj/Debug/net10.0/refint/HoneyBox.Core.dll and b/server/C#/HoneyBox/src/HoneyBox.Core/obj/Debug/net10.0/refint/HoneyBox.Core.dll differ diff --git a/server/C#/HoneyBox/src/HoneyBox.Infrastructure/Modules/ServiceModule.cs b/server/C#/HoneyBox/src/HoneyBox.Infrastructure/Modules/ServiceModule.cs index fdc850a3..f285fca6 100644 --- a/server/C#/HoneyBox/src/HoneyBox.Infrastructure/Modules/ServiceModule.cs +++ b/server/C#/HoneyBox/src/HoneyBox.Infrastructure/Modules/ServiceModule.cs @@ -116,5 +116,41 @@ public class ServiceModule : Module var logger = c.Resolve>(); return new WelfareService(dbContext, logger); }).As().InstancePerLifetimeScope(); + + // ========== 商品系统服务注册 ========== + + // 注册商品缓存服务 + builder.Register(c => + { + var redisService = c.Resolve(); + var logger = c.Resolve>(); + return new GoodsCacheService(redisService, logger); + }).As().InstancePerLifetimeScope(); + + // 注册商品服务 + builder.Register(c => + { + var dbContext = c.Resolve(); + var cacheService = c.Resolve(); + var logger = c.Resolve>(); + return new GoodsService(dbContext, cacheService, logger); + }).As().InstancePerLifetimeScope(); + + // 注册收藏服务 + builder.Register(c => + { + var dbContext = c.Resolve(); + var logger = c.Resolve>(); + var redisService = c.Resolve(); + return new CollectionService(dbContext, logger, redisService); + }).As().InstancePerLifetimeScope(); + + // 注册奖品服务 + builder.Register(c => + { + var dbContext = c.Resolve(); + var logger = c.Resolve>(); + return new PrizeService(dbContext, logger); + }).As().InstancePerLifetimeScope(); } } diff --git a/server/C#/HoneyBox/src/HoneyBox.Infrastructure/bin/Debug/net10.0/HoneyBox.Core.dll b/server/C#/HoneyBox/src/HoneyBox.Infrastructure/bin/Debug/net10.0/HoneyBox.Core.dll index 82fbad46..eb1d495c 100644 Binary files a/server/C#/HoneyBox/src/HoneyBox.Infrastructure/bin/Debug/net10.0/HoneyBox.Core.dll and b/server/C#/HoneyBox/src/HoneyBox.Infrastructure/bin/Debug/net10.0/HoneyBox.Core.dll differ diff --git a/server/C#/HoneyBox/src/HoneyBox.Infrastructure/bin/Debug/net10.0/HoneyBox.Core.pdb b/server/C#/HoneyBox/src/HoneyBox.Infrastructure/bin/Debug/net10.0/HoneyBox.Core.pdb index f37d0d47..5ea93cf6 100644 Binary files a/server/C#/HoneyBox/src/HoneyBox.Infrastructure/bin/Debug/net10.0/HoneyBox.Core.pdb and b/server/C#/HoneyBox/src/HoneyBox.Infrastructure/bin/Debug/net10.0/HoneyBox.Core.pdb differ diff --git a/server/C#/HoneyBox/src/HoneyBox.Infrastructure/bin/Debug/net10.0/HoneyBox.Infrastructure.dll b/server/C#/HoneyBox/src/HoneyBox.Infrastructure/bin/Debug/net10.0/HoneyBox.Infrastructure.dll index a7e55fc8..dc9fa648 100644 Binary files a/server/C#/HoneyBox/src/HoneyBox.Infrastructure/bin/Debug/net10.0/HoneyBox.Infrastructure.dll and b/server/C#/HoneyBox/src/HoneyBox.Infrastructure/bin/Debug/net10.0/HoneyBox.Infrastructure.dll differ diff --git a/server/C#/HoneyBox/src/HoneyBox.Infrastructure/bin/Debug/net10.0/HoneyBox.Infrastructure.pdb b/server/C#/HoneyBox/src/HoneyBox.Infrastructure/bin/Debug/net10.0/HoneyBox.Infrastructure.pdb index 448f556c..de06c9ca 100644 Binary files a/server/C#/HoneyBox/src/HoneyBox.Infrastructure/bin/Debug/net10.0/HoneyBox.Infrastructure.pdb and b/server/C#/HoneyBox/src/HoneyBox.Infrastructure/bin/Debug/net10.0/HoneyBox.Infrastructure.pdb differ diff --git a/server/C#/HoneyBox/src/HoneyBox.Infrastructure/bin/Debug/net10.0/HoneyBox.Model.dll b/server/C#/HoneyBox/src/HoneyBox.Infrastructure/bin/Debug/net10.0/HoneyBox.Model.dll index e8943c95..1b4d5a5e 100644 Binary files a/server/C#/HoneyBox/src/HoneyBox.Infrastructure/bin/Debug/net10.0/HoneyBox.Model.dll and b/server/C#/HoneyBox/src/HoneyBox.Infrastructure/bin/Debug/net10.0/HoneyBox.Model.dll differ diff --git a/server/C#/HoneyBox/src/HoneyBox.Infrastructure/bin/Debug/net10.0/HoneyBox.Model.pdb b/server/C#/HoneyBox/src/HoneyBox.Infrastructure/bin/Debug/net10.0/HoneyBox.Model.pdb index eee28ec8..e48e680e 100644 Binary files a/server/C#/HoneyBox/src/HoneyBox.Infrastructure/bin/Debug/net10.0/HoneyBox.Model.pdb and b/server/C#/HoneyBox/src/HoneyBox.Infrastructure/bin/Debug/net10.0/HoneyBox.Model.pdb differ diff --git a/server/C#/HoneyBox/src/HoneyBox.Infrastructure/obj/Debug/net10.0/HoneyBox.Infrastructure.AssemblyInfo.cs b/server/C#/HoneyBox/src/HoneyBox.Infrastructure/obj/Debug/net10.0/HoneyBox.Infrastructure.AssemblyInfo.cs index fe90cac3..53e50766 100644 --- a/server/C#/HoneyBox/src/HoneyBox.Infrastructure/obj/Debug/net10.0/HoneyBox.Infrastructure.AssemblyInfo.cs +++ b/server/C#/HoneyBox/src/HoneyBox.Infrastructure/obj/Debug/net10.0/HoneyBox.Infrastructure.AssemblyInfo.cs @@ -13,7 +13,7 @@ using System.Reflection; [assembly: System.Reflection.AssemblyCompanyAttribute("HoneyBox.Infrastructure")] [assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")] [assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")] -[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+21c4bb5c6a0c35c345420f3a29d64fd2922e7427")] +[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+7e00d28ad48928059f0ff514ed804f2cb0626442")] [assembly: System.Reflection.AssemblyProductAttribute("HoneyBox.Infrastructure")] [assembly: System.Reflection.AssemblyTitleAttribute("HoneyBox.Infrastructure")] [assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")] diff --git a/server/C#/HoneyBox/src/HoneyBox.Infrastructure/obj/Debug/net10.0/HoneyBox.Infrastructure.dll b/server/C#/HoneyBox/src/HoneyBox.Infrastructure/obj/Debug/net10.0/HoneyBox.Infrastructure.dll index a7e55fc8..dc9fa648 100644 Binary files a/server/C#/HoneyBox/src/HoneyBox.Infrastructure/obj/Debug/net10.0/HoneyBox.Infrastructure.dll and b/server/C#/HoneyBox/src/HoneyBox.Infrastructure/obj/Debug/net10.0/HoneyBox.Infrastructure.dll differ diff --git a/server/C#/HoneyBox/src/HoneyBox.Infrastructure/obj/Debug/net10.0/HoneyBox.Infrastructure.pdb b/server/C#/HoneyBox/src/HoneyBox.Infrastructure/obj/Debug/net10.0/HoneyBox.Infrastructure.pdb index 448f556c..de06c9ca 100644 Binary files a/server/C#/HoneyBox/src/HoneyBox.Infrastructure/obj/Debug/net10.0/HoneyBox.Infrastructure.pdb and b/server/C#/HoneyBox/src/HoneyBox.Infrastructure/obj/Debug/net10.0/HoneyBox.Infrastructure.pdb differ diff --git a/server/C#/HoneyBox/src/HoneyBox.Infrastructure/obj/Debug/net10.0/ref/HoneyBox.Infrastructure.dll b/server/C#/HoneyBox/src/HoneyBox.Infrastructure/obj/Debug/net10.0/ref/HoneyBox.Infrastructure.dll index bebe8379..abe23c0f 100644 Binary files a/server/C#/HoneyBox/src/HoneyBox.Infrastructure/obj/Debug/net10.0/ref/HoneyBox.Infrastructure.dll and b/server/C#/HoneyBox/src/HoneyBox.Infrastructure/obj/Debug/net10.0/ref/HoneyBox.Infrastructure.dll differ diff --git a/server/C#/HoneyBox/src/HoneyBox.Infrastructure/obj/Debug/net10.0/refint/HoneyBox.Infrastructure.dll b/server/C#/HoneyBox/src/HoneyBox.Infrastructure/obj/Debug/net10.0/refint/HoneyBox.Infrastructure.dll index bebe8379..abe23c0f 100644 Binary files a/server/C#/HoneyBox/src/HoneyBox.Infrastructure/obj/Debug/net10.0/refint/HoneyBox.Infrastructure.dll and b/server/C#/HoneyBox/src/HoneyBox.Infrastructure/obj/Debug/net10.0/refint/HoneyBox.Infrastructure.dll differ diff --git a/server/C#/HoneyBox/src/HoneyBox.Model/Data/HoneyBoxDbContext.cs b/server/C#/HoneyBox/src/HoneyBox.Model/Data/HoneyBoxDbContext.cs index 4e952015..9cac7208 100644 --- a/server/C#/HoneyBox/src/HoneyBox.Model/Data/HoneyBoxDbContext.cs +++ b/server/C#/HoneyBox/src/HoneyBox.Model/Data/HoneyBoxDbContext.cs @@ -94,6 +94,10 @@ public partial class HoneyBoxDbContext : DbContext public virtual DbSet RedeemCodes { get; set; } + public virtual DbSet GoodsCollections { get; set; } + + public virtual DbSet GoodsLocks { get; set; } + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { // Connection string is configured in Program.cs via dependency injection @@ -2675,6 +2679,67 @@ public partial class HoneyBoxDbContext : DbContext .HasColumnName("remark"); }); + modelBuilder.Entity(entity => + { + entity.HasKey(e => e.Id).HasName("pk_goods_collections"); + + entity.ToTable("goods_collections", tb => tb.HasComment("商品收藏表,存储用户收藏的商品信息")); + + entity.HasIndex(e => e.UserId, "ix_goods_collections_user_id"); + + entity.HasIndex(e => e.GoodsId, "ix_goods_collections_goods_id"); + + entity.HasIndex(e => new { e.UserId, e.GoodsId, e.Num }, "uk_goods_collections_user_goods_num").IsUnique(); + + entity.Property(e => e.Id) + .HasComment("收藏ID") + .HasColumnName("id"); + entity.Property(e => e.UserId) + .HasComment("用户ID") + .HasColumnName("user_id"); + entity.Property(e => e.GoodsId) + .HasComment("商品ID") + .HasColumnName("goods_id"); + entity.Property(e => e.Num) + .HasComment("箱号") + .HasColumnName("num"); + entity.Property(e => e.Type) + .HasComment("商品类型 1-一番赏 2-无限赏 3-擂台赏 4-抽卡机 5-积分赏 6-全局赏 7-福利盲盒 8-领主赏 9-连击赏") + .HasColumnName("type"); + entity.Property(e => e.CreatedAt) + .HasDefaultValueSql("(getdate())") + .HasComment("添加时间") + .HasColumnName("created_at"); + }); + + modelBuilder.Entity(entity => + { + entity.HasKey(e => e.Id).HasName("pk_goods_locks"); + + entity.ToTable("goods_locks", tb => tb.HasComment("商品锁箱信息表,存储盲盒锁箱状态")); + + entity.HasIndex(e => e.UserId, "ix_goods_locks_user_id"); + + entity.HasIndex(e => e.GoodsIdNum, "ix_goods_locks_goods_id_num"); + + entity.Property(e => e.Id) + .HasComment("锁箱ID") + .HasColumnName("id"); + entity.Property(e => e.UserId) + .HasComment("用户ID") + .HasColumnName("user_id"); + entity.Property(e => e.GoodsIdNum) + .HasMaxLength(20) + .HasComment("商品ID和箱号组合") + .HasColumnName("goods_id_num"); + entity.Property(e => e.EndTime) + .HasComment("过期时间") + .HasColumnName("endtime"); + entity.Property(e => e.UpdateTime) + .HasComment("更新时间") + .HasColumnName("update_time"); + }); + OnModelCreatingPartial(modelBuilder); } diff --git a/server/C#/HoneyBox/src/HoneyBox.Model/Entities/GoodsCollection.cs b/server/C#/HoneyBox/src/HoneyBox.Model/Entities/GoodsCollection.cs new file mode 100644 index 00000000..5f766c33 --- /dev/null +++ b/server/C#/HoneyBox/src/HoneyBox.Model/Entities/GoodsCollection.cs @@ -0,0 +1,39 @@ +using System; + +namespace HoneyBox.Model.Entities; + +/// +/// 商品收藏表,存储用户收藏的商品信息 +/// +public partial class GoodsCollection +{ + /// + /// 收藏ID + /// + public int Id { get; set; } + + /// + /// 用户ID + /// + public int UserId { get; set; } + + /// + /// 商品ID + /// + public int GoodsId { get; set; } + + /// + /// 箱号 + /// + public int Num { get; set; } + + /// + /// 商品类型 1-一番赏 2-无限赏 3-擂台赏 4-抽卡机 5-积分赏 6-全局赏 7-福利盲盒 8-领主赏 9-连击赏 + /// + public byte Type { get; set; } + + /// + /// 添加时间 + /// + public DateTime CreatedAt { get; set; } +} diff --git a/server/C#/HoneyBox/src/HoneyBox.Model/Entities/GoodsLock.cs b/server/C#/HoneyBox/src/HoneyBox.Model/Entities/GoodsLock.cs new file mode 100644 index 00000000..b89cbec8 --- /dev/null +++ b/server/C#/HoneyBox/src/HoneyBox.Model/Entities/GoodsLock.cs @@ -0,0 +1,34 @@ +using System; + +namespace HoneyBox.Model.Entities; + +/// +/// 商品锁箱信息表,存储盲盒锁箱状态 +/// +public partial class GoodsLock +{ + /// + /// 锁箱ID + /// + public int Id { get; set; } + + /// + /// 用户ID + /// + public int UserId { get; set; } + + /// + /// 商品ID和箱号组合 (格式: goods_id_num) + /// + public string GoodsIdNum { get; set; } = null!; + + /// + /// 过期时间 (Unix时间戳) + /// + public long EndTime { get; set; } + + /// + /// 更新时间 (Unix时间戳) + /// + public long UpdateTime { get; set; } +} diff --git a/server/C#/HoneyBox/src/HoneyBox.Model/Models/Asset/AssetModels.cs b/server/C#/HoneyBox/src/HoneyBox.Model/Models/Asset/AssetModels.cs index d6eb89fd..f08484e8 100644 --- a/server/C#/HoneyBox/src/HoneyBox.Model/Models/Asset/AssetModels.cs +++ b/server/C#/HoneyBox/src/HoneyBox.Model/Models/Asset/AssetModels.cs @@ -1,3 +1,5 @@ +using System.Text.Json.Serialization; + namespace HoneyBox.Model.Models.Asset; /// @@ -8,16 +10,19 @@ public class AssetRecordRequest /// /// 类型过滤 (0=全部) /// + [JsonPropertyName("type")] public int Type { get; set; } = 0; /// /// 页码 /// + [JsonPropertyName("page")] public int Page { get; set; } = 1; /// /// 每页数量 /// + [JsonPropertyName("limit")] public int Limit { get; set; } = 15; } @@ -29,41 +34,48 @@ public class AssetRecordDto /// /// 变动金额 (带正负号) /// + [JsonPropertyName("change_money")] public string ChangeMoney { get; set; } = string.Empty; /// /// 变动说明 /// + [JsonPropertyName("content")] public string Content { get; set; } = string.Empty; /// /// 添加时间 (格式: Y-m-d H:i:s) /// + [JsonPropertyName("addtime")] public string AddTime { get; set; } = string.Empty; } /// -/// 资产记录分页响应 +/// 资产记录分页响应 (兼容PHP API格式) /// public class AssetRecordPageResponse { /// /// 记录列表 /// + [JsonPropertyName("list")] public List List { get; set; } = new(); /// /// 最后一页页码 /// + [JsonPropertyName("last_page")] public int LastPage { get; set; } /// /// 当前页码 /// + [JsonPropertyName("current_page")] public int CurrentPage { get; set; } /// /// 总记录数 /// + [JsonPropertyName("total")] public int Total { get; set; } } diff --git a/server/C#/HoneyBox/src/HoneyBox.Model/Models/Common.cs b/server/C#/HoneyBox/src/HoneyBox.Model/Models/Common.cs index ff0007e1..6dcc8287 100644 --- a/server/C#/HoneyBox/src/HoneyBox.Model/Models/Common.cs +++ b/server/C#/HoneyBox/src/HoneyBox.Model/Models/Common.cs @@ -1,3 +1,5 @@ +using System.Text.Json.Serialization; + namespace HoneyBox.Model.Models; /// @@ -8,16 +10,18 @@ public class PageRequest /// /// 页码,从1开始 /// + [JsonPropertyName("page")] public int Page { get; set; } = 1; /// /// 每页数量 /// + [JsonPropertyName("page_size")] public int PageSize { get; set; } = 10; } /// -/// 分页响应基类 +/// 分页响应基类 (兼容PHP API格式) /// /// 数据类型 public class PageResponse @@ -25,27 +29,32 @@ public class PageResponse /// /// 数据列表 /// - public List List { get; set; } = new(); + [JsonPropertyName("data")] + public List Data { get; set; } = new(); + + /// + /// 最后一页页码 (兼容PHP API) + /// + [JsonPropertyName("last_page")] + public int LastPage { get; set; } /// /// 总数量 /// + [JsonPropertyName("total")] public int Total { get; set; } /// /// 当前页码 /// + [JsonPropertyName("page")] public int Page { get; set; } /// /// 每页数量 /// + [JsonPropertyName("page_size")] public int PageSize { get; set; } - - /// - /// 总页数 - /// - public int TotalPages => PageSize > 0 ? (int)Math.Ceiling((double)Total / PageSize) : 0; } /// @@ -56,6 +65,7 @@ public class IdRequest /// /// ID /// + [JsonPropertyName("id")] public int Id { get; set; } } @@ -67,5 +77,6 @@ public class IdsRequest /// /// ID列表 /// + [JsonPropertyName("ids")] public List Ids { get; set; } = new(); } diff --git a/server/C#/HoneyBox/src/HoneyBox.Model/Models/Goods/GoodsModels.cs b/server/C#/HoneyBox/src/HoneyBox.Model/Models/Goods/GoodsModels.cs index 9b224642..d3b99757 100644 --- a/server/C#/HoneyBox/src/HoneyBox.Model/Models/Goods/GoodsModels.cs +++ b/server/C#/HoneyBox/src/HoneyBox.Model/Models/Goods/GoodsModels.cs @@ -1,6 +1,9 @@ +using System.Text.Json.Serialization; +using HoneyBox.Model.Models; + namespace HoneyBox.Model.Models.Goods; -using HoneyBox.Model.Models; +#region Request Models /// /// 商品列表请求 @@ -13,110 +16,903 @@ public class GoodsListRequest : PageRequest public int? CategoryId { get; set; } /// - /// 商品类型 + /// 商品类型 (-1表示全部) /// - public byte? Type { get; set; } + [JsonPropertyName("type")] + public int Type { get; set; } = -1; } /// -/// 商品列表项响应 +/// 商品详情请求 /// -public class GoodsListItemResponse +public class GoodsDetailRequest { /// /// 商品ID /// + [JsonPropertyName("goods_id")] + public int GoodsId { get; set; } + + /// + /// 箱号 (0表示自动选择) + /// + [JsonPropertyName("goods_num")] + public int GoodsNum { get; set; } = 0; +} + +/// +/// 商品子奖品请求 +/// +public class GoodsChildrenRequest +{ + /// + /// 商品ID + /// + [JsonPropertyName("goods_id")] + public int GoodsId { get; set; } + + /// + /// 箱号 + /// + [JsonPropertyName("goods_num")] + public int GoodsNum { get; set; } + + /// + /// 奖品列表ID + /// + [JsonPropertyName("goods_list_id")] + public int GoodsListId { get; set; } +} + +/// +/// 商品扩展配置请求 +/// +public class GoodsExtendRequest +{ + /// + /// 商品ID + /// + [JsonPropertyName("goods_id")] + public int GoodsId { get; set; } + + /// + /// 商品类型 + /// + [JsonPropertyName("goods_type")] + public int GoodsType { get; set; } +} + +/// +/// 箱号列表请求 +/// +public class BoxListRequest +{ + /// + /// 商品ID + /// + [JsonPropertyName("goods_id")] + public int GoodsId { get; set; } +} + +/// +/// 箱号详情请求 +/// +public class BoxDetailRequest +{ + /// + /// 商品ID + /// + [JsonPropertyName("goods_id")] + public int GoodsId { get; set; } + + /// + /// 页码 (每页10个箱号) + /// + [JsonPropertyName("page_no")] + public int PageNo { get; set; } = 0; + + /// + /// 排序方式: 0=箱号升序, 1=箱号降序, 2=余量降序 + /// + [JsonPropertyName("sort")] + public int Sort { get; set; } = 0; +} + +/// +/// 奖品数量统计请求 +/// +public class PrizeCountRequest +{ + /// + /// 商品ID + /// + [JsonPropertyName("goods_id")] + public int GoodsId { get; set; } +} + +/// +/// 奖品内容请求 +/// +public class PrizeContentRequest +{ + /// + /// 商品ID + /// + [JsonPropertyName("goods_id")] + public int GoodsId { get; set; } + + /// + /// 箱号 + /// + [JsonPropertyName("num")] + public int Num { get; set; } +} + +/// +/// 中奖记录请求 +/// +public class PrizeLogsRequest : PageRequest +{ + /// + /// 商品ID + /// + [JsonPropertyName("goods_id")] + public int GoodsId { get; set; } + + /// + /// 箱号 + /// + [JsonPropertyName("goods_num")] + public int GoodsNum { get; set; } + + /// + /// 赏品分类ID (0表示全部) + /// + [JsonPropertyName("shang_id")] + public int ShangId { get; set; } = 0; +} + +#endregion + +#region Response/DTO Models + +/// +/// 商品列表项DTO (兼容PHP API snake_case格式) +/// +public class GoodsListDto +{ + /// + /// 商品ID + /// + [JsonPropertyName("id")] public int Id { get; set; } /// /// 商品标题 /// + [JsonPropertyName("title")] public string Title { get; set; } = string.Empty; /// /// 商品图片URL /// + [JsonPropertyName("img_url")] public string ImgUrl { get; set; } = string.Empty; /// /// 商品价格 /// - public decimal Price { get; set; } + [JsonPropertyName("price")] + public string Price { get; set; } = "0"; + + /// + /// 商品类型 + /// + [JsonPropertyName("type")] + public int Type { get; set; } + + /// + /// 类型文字 + /// + [JsonPropertyName("type_text")] + public string TypeText { get; set; } = string.Empty; + + /// + /// 总库存 + /// + [JsonPropertyName("stock")] + public int Stock { get; set; } + + /// + /// 已售库存 + /// + [JsonPropertyName("sale_stock")] + public int SaleStock { get; set; } + + /// + /// 状态 + /// + [JsonPropertyName("status")] + public int Status { get; set; } + + /// + /// 是否锁定 + /// + [JsonPropertyName("lock_is")] + public int LockIs { get; set; } + + /// + /// 是否首折 + /// + [JsonPropertyName("is_shou_zhe")] + public int IsShouZhe { get; set; } + + /// + /// 是否新品 + /// + [JsonPropertyName("new_is")] + public int NewIs { get; set; } + + /// + /// 参与次数 + /// + [JsonPropertyName("join_count")] + public int JoinCount { get; set; } + + /// + /// 需要抽奖次数 + /// + [JsonPropertyName("need_draw_num")] + public int NeedDrawNum { get; set; } /// /// 显示价格 /// + [JsonPropertyName("show_price")] public string? ShowPrice { get; set; } /// - /// 商品类型 + /// 角标文字 /// - public byte Type { get; set; } + [JsonPropertyName("corner_text")] + public string? CornerText { get; set; } } +/// +/// 商品信息DTO +/// +public class GoodsInfoDto +{ + [JsonPropertyName("id")] + public int Id { get; set; } + + [JsonPropertyName("title")] + public string Title { get; set; } = string.Empty; + + [JsonPropertyName("imgurl_detail")] + public string ImgUrlDetail { get; set; } = string.Empty; + + [JsonPropertyName("price")] + public string Price { get; set; } = "0"; + + [JsonPropertyName("stock")] + public int Stock { get; set; } + + [JsonPropertyName("sale_stock")] + public int SaleStock { get; set; } + + [JsonPropertyName("surplus_stock")] + public int SurplusStock { get; set; } + + [JsonPropertyName("goodslist_stock")] + public int GoodslistStock { get; set; } + + [JsonPropertyName("goodslist_surplus_stock")] + public int GoodslistSurplusStock { get; set; } + + [JsonPropertyName("type")] + public int Type { get; set; } + + [JsonPropertyName("type_text")] + public string? TypeText { get; set; } + + [JsonPropertyName("status")] + public int Status { get; set; } + + [JsonPropertyName("lock_is")] + public int LockIs { get; set; } + + [JsonPropertyName("num")] + public int Num { get; set; } + + [JsonPropertyName("addtime")] + public string? Addtime { get; set; } + + [JsonPropertyName("goods_describe")] + public string? GoodsDescribe { get; set; } + + [JsonPropertyName("coupon_is")] + public int CouponIs { get; set; } + + [JsonPropertyName("coupon_pro")] + public int CouponPro { get; set; } + + [JsonPropertyName("integral_is")] + public int IntegralIs { get; set; } + + [JsonPropertyName("rage_is")] + public int RageIs { get; set; } + + [JsonPropertyName("rage")] + public int Rage { get; set; } + + [JsonPropertyName("lingzhu_is")] + public int LingzhuIs { get; set; } + + [JsonPropertyName("lingzhu_fan")] + public int LingzhuFan { get; set; } + + [JsonPropertyName("card_num")] + public int CardNum { get; set; } + + [JsonPropertyName("is_shou_zhe")] + public int IsShouZhe { get; set; } + + [JsonPropertyName("new_is")] + public int NewIs { get; set; } + + [JsonPropertyName("collection_is")] + public int CollectionIs { get; set; } + + [JsonPropertyName("three_time")] + public int ThreeTime { get; set; } + + [JsonPropertyName("five_time")] + public int FiveTime { get; set; } + + [JsonPropertyName("quanju_xiangou")] + public int QuanjuXiangou { get; set; } + + [JsonPropertyName("daily_xiangou")] + public int DailyXiangou { get; set; } +} + + /// /// 商品详情响应 /// -public class GoodsDetailResponse +public class GoodsDetailResponseDto { /// - /// 商品ID + /// 商品信息 /// - public int Id { get; set; } + [JsonPropertyName("goods")] + public GoodsInfoDto Goods { get; set; } = new(); /// - /// 商品标题 + /// 锁箱信息 /// - public string Title { get; set; } = string.Empty; + [JsonPropertyName("lock_info")] + public LockInfoDto LockInfo { get; set; } = new(); /// - /// 商品图片URL + /// 参与用户头像列表 /// - public string ImgUrl { get; set; } = string.Empty; + [JsonPropertyName("join_user")] + public List JoinUser { get; set; } = new(); /// - /// 商品详情图片URL + /// 参与次数 /// - public string ImgUrlDetail { get; set; } = string.Empty; - - /// - /// 商品价格 - /// - public decimal Price { get; set; } - - /// - /// 商品描述 - /// - public string? GoodsDescribe { get; set; } + [JsonPropertyName("join_count")] + public int JoinCount { get; set; } /// /// 奖品列表 /// - public List Prizes { get; set; } = new(); + [JsonPropertyName("goodslist")] + public List GoodsList { get; set; } = new(); + + /// + /// 限购信息 + /// + [JsonPropertyName("limit_info")] + public LimitInfoDto LimitInfo { get; set; } = new(); } /// -/// 商品奖品项响应 +/// 锁箱信息DTO /// -public class GoodsPrizeItemResponse +public class LockInfoDto { /// - /// 奖品ID + /// 是否锁定 /// + [JsonPropertyName("lock_is")] + public int LockIs { get; set; } + + /// + /// 锁定用户昵称 + /// + [JsonPropertyName("goods_lock_user_nickname")] + public string? GoodsLockUserNickname { get; set; } + + /// + /// 锁定用户头像 + /// + [JsonPropertyName("goods_lock_user_headimg")] + public string? GoodsLockUserHeadimg { get; set; } + + /// + /// 锁箱剩余时间 (Unix时间戳) + /// + [JsonPropertyName("goods_lock_surplus_time")] + public long GoodsLockSurplusTime { get; set; } +} + +/// +/// 限购信息DTO +/// +public class LimitInfoDto +{ + /// + /// 每日限购数量 + /// + [JsonPropertyName("daily_xiangou")] + public int DailyXiangou { get; set; } + + /// + /// 今日已购数量 + /// + [JsonPropertyName("daily_bought")] + public int DailyBought { get; set; } + + /// + /// 今日剩余可购数量 + /// + [JsonPropertyName("daily_remaining")] + public int DailyRemaining { get; set; } + + /// + /// 全局限购数量 + /// + [JsonPropertyName("quanju_xiangou")] + public int QuanjuXiangou { get; set; } + + /// + /// 全局已购数量 + /// + [JsonPropertyName("global_bought")] + public int GlobalBought { get; set; } + + /// + /// 全局剩余可购数量 + /// + [JsonPropertyName("global_remaining")] + public int GlobalRemaining { get; set; } +} + +/// +/// 奖品列表项DTO +/// +public class GoodsListItemDto +{ + [JsonPropertyName("id")] public int Id { get; set; } - /// - /// 奖品名称 - /// - public string Name { get; set; } = string.Empty; + [JsonPropertyName("shang_id")] + public int ShangId { get; set; } - /// - /// 奖品图片 - /// + [JsonPropertyName("shang_info")] + public ShangInfoDto? ShangInfo { get; set; } + + [JsonPropertyName("title")] + public string Title { get; set; } = string.Empty; + + [JsonPropertyName("stock")] + public int Stock { get; set; } + + [JsonPropertyName("surplus_stock")] + public int SurplusStock { get; set; } + + [JsonPropertyName("img_url")] public string ImgUrl { get; set; } = string.Empty; - /// - /// 奖品等级 - /// - public int Level { get; set; } + [JsonPropertyName("goods_type")] + public int GoodsType { get; set; } + + [JsonPropertyName("price")] + public string Price { get; set; } = "0"; + + [JsonPropertyName("sc_money")] + public string ScMoney { get; set; } = "0"; + + [JsonPropertyName("sale_time")] + public string? SaleTime { get; set; } + + [JsonPropertyName("pro")] + public string Pro { get; set; } = "0"; + + [JsonPropertyName("children")] + public bool Children { get; set; } + + [JsonPropertyName("real_pro")] + public string RealPro { get; set; } = "0"; } + +/// +/// 赏品等级信息DTO +/// +public class ShangInfoDto +{ + [JsonPropertyName("id")] + public int Id { get; set; } + + [JsonPropertyName("title")] + public string Title { get; set; } = string.Empty; + + [JsonPropertyName("img_url")] + public string? ImgUrl { get; set; } + + [JsonPropertyName("color")] + public string? Color { get; set; } + + [JsonPropertyName("special_img_url")] + public string? SpecialImgUrl { get; set; } +} + +/// +/// 子奖品DTO +/// +public class GoodsChildrenDto +{ + [JsonPropertyName("id")] + public int Id { get; set; } + + [JsonPropertyName("shang_id")] + public int ShangId { get; set; } + + [JsonPropertyName("title")] + public string Title { get; set; } = string.Empty; + + [JsonPropertyName("imgurl")] + public string ImgUrl { get; set; } = string.Empty; + + [JsonPropertyName("stock")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] + public int Stock { get; set; } + + [JsonPropertyName("surplus_stock")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] + public int SurplusStock { get; set; } + + [JsonPropertyName("goods_type")] + public int GoodsType { get; set; } + + [JsonPropertyName("sale_time")] + public string? SaleTime { get; set; } + + [JsonPropertyName("price")] + public string Price { get; set; } = "0"; + + [JsonPropertyName("sc_money")] + public string ScMoney { get; set; } = "0"; + + [JsonPropertyName("real_pro")] + public string RealPro { get; set; } = "0"; + + [JsonPropertyName("pro")] + public string Pro { get; set; } = "0"; + + [JsonPropertyName("pro_num")] + public string ProNum { get; set; } = "0"; + + [JsonPropertyName("shang_info")] + public ShangInfoDto? ShangInfo { get; set; } +} + +/// +/// 商品扩展配置DTO +/// +public class GoodsExtendDto +{ + [JsonPropertyName("pay_wechat")] + public int PayWechat { get; set; } + + [JsonPropertyName("pay_balance")] + public int PayBalance { get; set; } + + [JsonPropertyName("pay_currency")] + public int PayCurrency { get; set; } + + [JsonPropertyName("pay_currency2")] + public int PayCurrency2 { get; set; } + + [JsonPropertyName("pay_coupon")] + public int PayCoupon { get; set; } + + [JsonPropertyName("is_deduction")] + public int IsDeduction { get; set; } +} + +/// +/// 箱号分组DTO +/// +public class BoxGroupDto +{ + /// + /// 显示标题,如 "1-10" + /// + [JsonPropertyName("title")] + public string Title { get; set; } = string.Empty; + + /// + /// 页码(从0开始) + /// + [JsonPropertyName("page_no")] + public int PageNo { get; set; } +} + +/// +/// 箱号详情DTO +/// +public class BoxDetailDto +{ + [JsonPropertyName("num")] + public int Num { get; set; } + + [JsonPropertyName("surplus_all_stock")] + public int SurplusAllStock { get; set; } + + [JsonPropertyName("goodslist")] + public List GoodsList { get; set; } = new(); +} + +/// +/// 箱号商品列表DTO +/// +public class BoxGoodsListDto +{ + [JsonPropertyName("id")] + public int Id { get; set; } + + [JsonPropertyName("shang_id")] + public int ShangId { get; set; } + + [JsonPropertyName("shang_info")] + public ShangInfoDto? ShangInfo { get; set; } + + [JsonPropertyName("title")] + public string Title { get; set; } = string.Empty; + + [JsonPropertyName("surplus_stock")] + public int SurplusStock { get; set; } + + [JsonPropertyName("stock")] + public int Stock { get; set; } +} + + +#endregion + +#region Collection Models + +/// +/// 收藏请求 +/// +public class CollectionRequest +{ + /// + /// 商品ID + /// + [JsonPropertyName("goods_id")] + public int GoodsId { get; set; } + + /// + /// 箱号 + /// + [JsonPropertyName("goods_num")] + public int GoodsNum { get; set; } + + /// + /// 操作类型: add=收藏, remove=取消收藏 + /// + [JsonPropertyName("action")] + public string Action { get; set; } = "add"; +} + +/// +/// 收藏列表请求 +/// +public class CollectionListRequest : PageRequest +{ + /// + /// 商品类型 (0=全部) + /// + [JsonPropertyName("type")] + public int Type { get; set; } = 0; +} + +/// +/// 收藏列表响应 (兼容PHP API格式) +/// +public class CollectionListResponse +{ + [JsonPropertyName("data")] + public List Data { get; set; } = new(); + + [JsonPropertyName("last_page")] + public int LastPage { get; set; } +} + +/// +/// 收藏DTO (兼容PHP API格式) +/// +public class CollectionDto +{ + [JsonPropertyName("id")] + public int Id { get; set; } + + [JsonPropertyName("goods_id")] + public int GoodsId { get; set; } + + [JsonPropertyName("type")] + public int Type { get; set; } + + [JsonPropertyName("num")] + public int Num { get; set; } + + [JsonPropertyName("goods_title")] + public string GoodsTitle { get; set; } = string.Empty; + + [JsonPropertyName("goods_price")] + public string GoodsPrice { get; set; } = "0"; + + [JsonPropertyName("imgurl")] + public string ImgUrl { get; set; } = string.Empty; + + [JsonPropertyName("stock")] + public int Stock { get; set; } + + [JsonPropertyName("surplus_stock")] + public int SurplusStock { get; set; } +} + +/// +/// 删除收藏请求 +/// +public class DeleteCollectionRequest +{ + /// + /// 收藏记录ID + /// + [JsonPropertyName("id")] + public int Id { get; set; } +} + +#endregion + +#region Prize Statistics Models + +/// +/// 奖品数量统计响应 +/// +public class PrizeCountResponseDto +{ + [JsonPropertyName("list")] + public List List { get; set; } = new(); + + [JsonPropertyName("total")] + public int Total { get; set; } + + [JsonPropertyName("surplus")] + public int Surplus { get; set; } +} + +/// +/// 奖品数量统计项DTO +/// +public class PrizeCountItemDto +{ + [JsonPropertyName("shang_id")] + public int ShangId { get; set; } + + [JsonPropertyName("shang_info")] + public ShangInfoDto? ShangInfo { get; set; } + + [JsonPropertyName("total")] + public int Total { get; set; } + + [JsonPropertyName("surplus")] + public int Surplus { get; set; } +} + +/// +/// 奖品内容响应 +/// +public class PrizeContentResponseDto +{ + [JsonPropertyName("list")] + public List List { get; set; } = new(); +} + +#endregion + +#region Prize Logs Models + +/// +/// 中奖记录响应 +/// +public class PrizeLogsResponseDto +{ + [JsonPropertyName("category")] + public List Category { get; set; } = new(); + + [JsonPropertyName("data")] + public List Data { get; set; } = new(); + + [JsonPropertyName("last_page")] + public int LastPage { get; set; } + + [JsonPropertyName("total")] + public int Total { get; set; } +} + +/// +/// 分类DTO - 用于中奖记录筛选 +/// +public class CategoryDto +{ + [JsonPropertyName("shang_id")] + public int ShangId { get; set; } + + [JsonPropertyName("shang_title")] + public string ShangTitle { get; set; } = string.Empty; +} + +/// +/// 中奖记录DTO - 匹配PHP API响应格式 +/// +public class PrizeLogDto +{ + [JsonPropertyName("user_id")] + public int UserId { get; set; } + + [JsonPropertyName("goodslist_title")] + public string GoodslistTitle { get; set; } = string.Empty; + + [JsonPropertyName("goodslist_imgurl")] + public string GoodslistImgurl { get; set; } = string.Empty; + + [JsonPropertyName("shang_id")] + public int ShangId { get; set; } + + [JsonPropertyName("addtime")] + public string Addtime { get; set; } = string.Empty; + + [JsonPropertyName("prize_num")] + public int PrizeNum { get; set; } + + [JsonPropertyName("shang_title")] + public string ShangTitle { get; set; } = string.Empty; + + [JsonPropertyName("user_info")] + public PrizeLogUserDto? UserInfo { get; set; } + + [JsonPropertyName("shang_color")] + public string ShangColor { get; set; } = string.Empty; +} + +/// +/// 中奖记录用户信息DTO +/// +public class PrizeLogUserDto +{ + [JsonPropertyName("nickname")] + public string Nickname { get; set; } = string.Empty; + + [JsonPropertyName("headimg")] + public string HeadImg { get; set; } = string.Empty; +} + +#endregion diff --git a/server/C#/HoneyBox/src/HoneyBox.Model/Models/Prize/PrizeModels.cs b/server/C#/HoneyBox/src/HoneyBox.Model/Models/Prize/PrizeModels.cs index de4190cc..f4c0ffe2 100644 --- a/server/C#/HoneyBox/src/HoneyBox.Model/Models/Prize/PrizeModels.cs +++ b/server/C#/HoneyBox/src/HoneyBox.Model/Models/Prize/PrizeModels.cs @@ -1,5 +1,6 @@ namespace HoneyBox.Model.Models.Prize; +using System.Text.Json.Serialization; using HoneyBox.Model.Models; /// @@ -10,11 +11,13 @@ public class DrawPrizeRequest /// /// 商品ID /// + [JsonPropertyName("goods_id")] public int GoodsId { get; set; } /// /// 抽奖次数 /// + [JsonPropertyName("times")] public int Times { get; set; } = 1; } @@ -26,16 +29,19 @@ public class DrawPrizeResponse /// /// 中奖奖品列表 /// + [JsonPropertyName("prizes")] public List Prizes { get; set; } = new(); /// /// 消耗金额 /// + [JsonPropertyName("cost_amount")] public decimal CostAmount { get; set; } /// /// 剩余余额 /// + [JsonPropertyName("remaining_balance")] public decimal RemainingBalance { get; set; } } @@ -47,26 +53,31 @@ public class PrizeResultItem /// /// 奖品ID /// + [JsonPropertyName("id")] public int Id { get; set; } /// /// 奖品名称 /// + [JsonPropertyName("name")] public string Name { get; set; } = string.Empty; /// /// 奖品图片 /// + [JsonPropertyName("img_url")] public string ImgUrl { get; set; } = string.Empty; /// /// 奖品等级 /// + [JsonPropertyName("level")] public int Level { get; set; } /// /// 奖品价值 /// + [JsonPropertyName("value")] public decimal Value { get; set; } } @@ -78,6 +89,7 @@ public class PrizeRecordListRequest : PageRequest /// /// 商品ID /// + [JsonPropertyName("goods_id")] public int? GoodsId { get; set; } } @@ -89,20 +101,24 @@ public class PrizeRecordResponse /// /// 记录ID /// + [JsonPropertyName("id")] public int Id { get; set; } /// /// 奖品名称 /// + [JsonPropertyName("prize_name")] public string PrizeName { get; set; } = string.Empty; /// /// 奖品图片 /// + [JsonPropertyName("prize_img")] public string PrizeImg { get; set; } = string.Empty; /// /// 中奖时间 /// + [JsonPropertyName("created_at")] public DateTime CreatedAt { get; set; } } diff --git a/server/C#/HoneyBox/src/HoneyBox.Model/bin/Debug/net8.0/HoneyBox.Model.dll b/server/C#/HoneyBox/src/HoneyBox.Model/bin/Debug/net8.0/HoneyBox.Model.dll index e8943c95..1b4d5a5e 100644 Binary files a/server/C#/HoneyBox/src/HoneyBox.Model/bin/Debug/net8.0/HoneyBox.Model.dll and b/server/C#/HoneyBox/src/HoneyBox.Model/bin/Debug/net8.0/HoneyBox.Model.dll differ diff --git a/server/C#/HoneyBox/src/HoneyBox.Model/bin/Debug/net8.0/HoneyBox.Model.pdb b/server/C#/HoneyBox/src/HoneyBox.Model/bin/Debug/net8.0/HoneyBox.Model.pdb index eee28ec8..e48e680e 100644 Binary files a/server/C#/HoneyBox/src/HoneyBox.Model/bin/Debug/net8.0/HoneyBox.Model.pdb and b/server/C#/HoneyBox/src/HoneyBox.Model/bin/Debug/net8.0/HoneyBox.Model.pdb differ diff --git a/server/C#/HoneyBox/src/HoneyBox.Model/obj/Debug/net8.0/HoneyBox.Model.AssemblyInfo.cs b/server/C#/HoneyBox/src/HoneyBox.Model/obj/Debug/net8.0/HoneyBox.Model.AssemblyInfo.cs index 12c5fd5b..e7c5659a 100644 --- a/server/C#/HoneyBox/src/HoneyBox.Model/obj/Debug/net8.0/HoneyBox.Model.AssemblyInfo.cs +++ b/server/C#/HoneyBox/src/HoneyBox.Model/obj/Debug/net8.0/HoneyBox.Model.AssemblyInfo.cs @@ -13,7 +13,7 @@ using System.Reflection; [assembly: System.Reflection.AssemblyCompanyAttribute("HoneyBox.Model")] [assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")] [assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")] -[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+21c4bb5c6a0c35c345420f3a29d64fd2922e7427")] +[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+7e00d28ad48928059f0ff514ed804f2cb0626442")] [assembly: System.Reflection.AssemblyProductAttribute("HoneyBox.Model")] [assembly: System.Reflection.AssemblyTitleAttribute("HoneyBox.Model")] [assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")] diff --git a/server/C#/HoneyBox/src/HoneyBox.Model/obj/Debug/net8.0/HoneyBox.Model.dll b/server/C#/HoneyBox/src/HoneyBox.Model/obj/Debug/net8.0/HoneyBox.Model.dll index e8943c95..1b4d5a5e 100644 Binary files a/server/C#/HoneyBox/src/HoneyBox.Model/obj/Debug/net8.0/HoneyBox.Model.dll and b/server/C#/HoneyBox/src/HoneyBox.Model/obj/Debug/net8.0/HoneyBox.Model.dll differ diff --git a/server/C#/HoneyBox/src/HoneyBox.Model/obj/Debug/net8.0/HoneyBox.Model.pdb b/server/C#/HoneyBox/src/HoneyBox.Model/obj/Debug/net8.0/HoneyBox.Model.pdb index eee28ec8..e48e680e 100644 Binary files a/server/C#/HoneyBox/src/HoneyBox.Model/obj/Debug/net8.0/HoneyBox.Model.pdb and b/server/C#/HoneyBox/src/HoneyBox.Model/obj/Debug/net8.0/HoneyBox.Model.pdb differ diff --git a/server/C#/HoneyBox/src/HoneyBox.Model/obj/Debug/net8.0/ref/HoneyBox.Model.dll b/server/C#/HoneyBox/src/HoneyBox.Model/obj/Debug/net8.0/ref/HoneyBox.Model.dll index 869bda28..bded5625 100644 Binary files a/server/C#/HoneyBox/src/HoneyBox.Model/obj/Debug/net8.0/ref/HoneyBox.Model.dll and b/server/C#/HoneyBox/src/HoneyBox.Model/obj/Debug/net8.0/ref/HoneyBox.Model.dll differ diff --git a/server/C#/HoneyBox/src/HoneyBox.Model/obj/Debug/net8.0/refint/HoneyBox.Model.dll b/server/C#/HoneyBox/src/HoneyBox.Model/obj/Debug/net8.0/refint/HoneyBox.Model.dll index 869bda28..bded5625 100644 Binary files a/server/C#/HoneyBox/src/HoneyBox.Model/obj/Debug/net8.0/refint/HoneyBox.Model.dll and b/server/C#/HoneyBox/src/HoneyBox.Model/obj/Debug/net8.0/refint/HoneyBox.Model.dll differ diff --git a/server/C#/HoneyBox/tests/HoneyBox.Tests/Integration/CollectionServiceIntegrationTests.cs b/server/C#/HoneyBox/tests/HoneyBox.Tests/Integration/CollectionServiceIntegrationTests.cs new file mode 100644 index 00000000..0fa9330d --- /dev/null +++ b/server/C#/HoneyBox/tests/HoneyBox.Tests/Integration/CollectionServiceIntegrationTests.cs @@ -0,0 +1,452 @@ +using HoneyBox.Core.Interfaces; +using HoneyBox.Core.Services; +using HoneyBox.Model.Data; +using HoneyBox.Model.Entities; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using Moq; +using Xunit; + +namespace HoneyBox.Tests.Integration; + +/// +/// 收藏服务集成测试 +/// 测试收藏/取消收藏流程 +/// Requirements: 6.1-6.4 +/// +public class CollectionServiceIntegrationTests +{ + private HoneyBoxDbContext CreateInMemoryDbContext() + { + var options = new DbContextOptionsBuilder() + .UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString()) + .Options; + + return new HoneyBoxDbContext(options); + } + + private CollectionService CreateCollectionService(HoneyBoxDbContext dbContext) + { + var mockLogger = new Mock>(); + var mockRedisService = new Mock(); + mockRedisService.Setup(x => x.DeleteAsync(It.IsAny())).Returns(Task.CompletedTask); + return new CollectionService(dbContext, mockLogger.Object, mockRedisService.Object); + } + + #region 收藏功能测试 (Requirements 6.1-6.4) + + /// + /// 测试添加收藏 + /// Requirements: 6.1 + /// + [Fact] + public async Task ToggleCollection_AddsCollection_WhenNotCollected() + { + // Arrange + var dbContext = CreateInMemoryDbContext(); + var service = CreateCollectionService(dbContext); + + var goods = new Good + { + Id = 1, + Title = "测试商品", + Type = 2, + Status = 1, + ShowIs = 0, + Price = 10, + Stock = 5, + ImgUrl = "img.jpg", + ImgUrlDetail = "detail.jpg" + }; + await dbContext.Goods.AddAsync(goods); + await dbContext.SaveChangesAsync(); + + // Act + var result = await service.ToggleCollectionAsync(100, 1, 1); + + // Assert + Assert.True(result); + var collection = await dbContext.GoodsCollections + .FirstOrDefaultAsync(c => c.UserId == 100 && c.GoodsId == 1 && c.Num == 1); + Assert.NotNull(collection); + } + + /// + /// 测试取消收藏 + /// Requirements: 6.2 + /// + [Fact] + public async Task ToggleCollection_RemovesCollection_WhenAlreadyCollected() + { + // Arrange + var dbContext = CreateInMemoryDbContext(); + var service = CreateCollectionService(dbContext); + + var goods = new Good + { + Id = 1, + Title = "测试商品", + Type = 2, + Status = 1, + ShowIs = 0, + Price = 10, + Stock = 5, + ImgUrl = "img.jpg", + ImgUrlDetail = "detail.jpg" + }; + await dbContext.Goods.AddAsync(goods); + + // 添加已有收藏 + var collection = new GoodsCollection + { + Id = 1, + UserId = 100, + GoodsId = 1, + Num = 1, + Type = 2, + CreatedAt = DateTime.Now + }; + await dbContext.GoodsCollections.AddAsync(collection); + await dbContext.SaveChangesAsync(); + + // Act - 再次调用应取消收藏 + var result = await service.ToggleCollectionAsync(100, 1, 1); + + // Assert + Assert.True(result); + var exists = await dbContext.GoodsCollections + .AnyAsync(c => c.UserId == 100 && c.GoodsId == 1 && c.Num == 1); + Assert.False(exists); + } + + /// + /// 测试收藏往返一致性 - 添加后再取消 + /// Requirements: 6.1, 6.2 + /// + [Fact] + public async Task ToggleCollection_RoundTrip_AddsAndRemoves() + { + // Arrange + var dbContext = CreateInMemoryDbContext(); + var service = CreateCollectionService(dbContext); + + var goods = new Good + { + Id = 1, + Title = "测试商品", + Type = 2, + Status = 1, + ShowIs = 0, + Price = 10, + Stock = 5, + ImgUrl = "img.jpg", + ImgUrlDetail = "detail.jpg" + }; + await dbContext.Goods.AddAsync(goods); + await dbContext.SaveChangesAsync(); + + // Act - 第一次调用:添加收藏 + await service.ToggleCollectionAsync(100, 1, 1); + var afterAdd = await service.IsCollectedAsync(100, 1, 1); + + // Act - 第二次调用:取消收藏 + await service.ToggleCollectionAsync(100, 1, 1); + var afterRemove = await service.IsCollectedAsync(100, 1, 1); + + // Assert + Assert.True(afterAdd); + Assert.False(afterRemove); + } + + /// + /// 测试收藏列表查询 + /// Requirements: 6.4 + /// + [Fact] + public async Task GetCollectionList_ReturnsUserCollections() + { + // Arrange + var dbContext = CreateInMemoryDbContext(); + var service = CreateCollectionService(dbContext); + + // 添加商品 + var goods = new List + { + new() { Id = 1, Title = "商品1", Type = 2, Status = 1, ShowIs = 0, Price = 10, Stock = 5, ImgUrl = "img1.jpg", ImgUrlDetail = "detail1.jpg" }, + new() { Id = 2, Title = "商品2", Type = 2, Status = 1, ShowIs = 0, Price = 20, Stock = 10, ImgUrl = "img2.jpg", ImgUrlDetail = "detail2.jpg" } + }; + await dbContext.Goods.AddRangeAsync(goods); + + // 添加收藏 + var collections = new List + { + new() { Id = 1, UserId = 100, GoodsId = 1, Num = 1, Type = 2, CreatedAt = DateTime.Now }, + new() { Id = 2, UserId = 100, GoodsId = 2, Num = 1, Type = 2, CreatedAt = DateTime.Now } + }; + await dbContext.GoodsCollections.AddRangeAsync(collections); + await dbContext.SaveChangesAsync(); + + // Act + var result = await service.GetCollectionListAsync(100, 0, 1, 10); + + // Assert + Assert.NotNull(result); + Assert.Equal(2, result.Data.Count); + } + + /// + /// 测试收藏列表查询 - 按类型过滤 + /// Requirements: 6.4 + /// + [Fact] + public async Task GetCollectionList_FiltersByType() + { + // Arrange + var dbContext = CreateInMemoryDbContext(); + var service = CreateCollectionService(dbContext); + + // 添加商品 + var goods = new List + { + new() { Id = 1, Title = "商品1", Type = 2, Status = 1, ShowIs = 0, Price = 10, Stock = 5, ImgUrl = "img1.jpg", ImgUrlDetail = "detail1.jpg" }, + new() { Id = 2, Title = "商品2", Type = 6, Status = 1, ShowIs = 0, Price = 20, Stock = 10, ImgUrl = "img2.jpg", ImgUrlDetail = "detail2.jpg" } + }; + await dbContext.Goods.AddRangeAsync(goods); + + // 添加收藏 + var collections = new List + { + new() { Id = 1, UserId = 100, GoodsId = 1, Num = 1, Type = 2, CreatedAt = DateTime.Now }, + new() { Id = 2, UserId = 100, GoodsId = 2, Num = 1, Type = 6, CreatedAt = DateTime.Now } + }; + await dbContext.GoodsCollections.AddRangeAsync(collections); + await dbContext.SaveChangesAsync(); + + // Act - 只查询类型2的收藏 + var result = await service.GetCollectionListAsync(100, 2, 1, 10); + + // Assert + Assert.Single(result.Data); + Assert.Equal(2, result.Data[0].Type); + } + + /// + /// 测试收藏列表查询 - 分页 + /// Requirements: 6.4 + /// + [Fact] + public async Task GetCollectionList_PaginationWorksCorrectly() + { + // Arrange + var dbContext = CreateInMemoryDbContext(); + var service = CreateCollectionService(dbContext); + + // 添加15个商品和收藏 + var goods = Enumerable.Range(1, 15).Select(i => new Good + { + Id = i, + Title = $"商品{i}", + Type = 2, + Status = 1, + ShowIs = 0, + Price = i * 10, + Stock = 10, + ImgUrl = $"img{i}.jpg", + ImgUrlDetail = $"detail{i}.jpg" + }).ToList(); + await dbContext.Goods.AddRangeAsync(goods); + + var collections = Enumerable.Range(1, 15).Select(i => new GoodsCollection + { + Id = i, + UserId = 100, + GoodsId = i, + Num = 1, + Type = 2, + CreatedAt = DateTime.Now.AddMinutes(-i) + }).ToList(); + await dbContext.GoodsCollections.AddRangeAsync(collections); + await dbContext.SaveChangesAsync(); + + // Act + var page1 = await service.GetCollectionListAsync(100, 0, 1, 10); + var page2 = await service.GetCollectionListAsync(100, 0, 2, 10); + + // Assert + Assert.Equal(2, page1.LastPage); + Assert.Equal(10, page1.Data.Count); + Assert.Equal(5, page2.Data.Count); + } + + /// + /// 测试检查收藏状态 + /// Requirements: 6.1 + /// + [Fact] + public async Task IsCollected_ReturnsCorrectStatus() + { + // Arrange + var dbContext = CreateInMemoryDbContext(); + var service = CreateCollectionService(dbContext); + + var collection = new GoodsCollection + { + Id = 1, + UserId = 100, + GoodsId = 1, + Num = 1, + Type = 2, + CreatedAt = DateTime.Now + }; + await dbContext.GoodsCollections.AddAsync(collection); + await dbContext.SaveChangesAsync(); + + // Act + var isCollected = await service.IsCollectedAsync(100, 1, 1); + var isNotCollected = await service.IsCollectedAsync(100, 2, 1); + + // Assert + Assert.True(isCollected); + Assert.False(isNotCollected); + } + + /// + /// 测试删除收藏 + /// Requirements: 6.2 + /// + [Fact] + public async Task DeleteCollection_RemovesCollectionById() + { + // Arrange + var dbContext = CreateInMemoryDbContext(); + var service = CreateCollectionService(dbContext); + + var collection = new GoodsCollection + { + Id = 1, + UserId = 100, + GoodsId = 1, + Num = 1, + Type = 2, + CreatedAt = DateTime.Now + }; + await dbContext.GoodsCollections.AddAsync(collection); + await dbContext.SaveChangesAsync(); + + // Act + var result = await service.DeleteCollectionAsync(100, 1); + + // Assert + Assert.True(result); + var exists = await dbContext.GoodsCollections.AnyAsync(c => c.Id == 1); + Assert.False(exists); + } + + /// + /// 测试删除不存在的收藏 + /// Requirements: 6.2 + /// + [Fact] + public async Task DeleteCollection_ThrowsException_WhenNotFound() + { + // Arrange + var dbContext = CreateInMemoryDbContext(); + var service = CreateCollectionService(dbContext); + + // Act & Assert + await Assert.ThrowsAsync( + () => service.DeleteCollectionAsync(100, 999)); + } + + /// + /// 测试收藏商品不存在 + /// Requirements: 6.1 + /// + [Fact] + public async Task ToggleCollection_ThrowsException_WhenGoodsNotFound() + { + // Arrange + var dbContext = CreateInMemoryDbContext(); + var service = CreateCollectionService(dbContext); + + // Act & Assert + await Assert.ThrowsAsync( + () => service.ToggleCollectionAsync(100, 999, 1)); + } + + /// + /// 测试收藏已下架商品 + /// Requirements: 6.1 + /// + [Fact] + public async Task ToggleCollection_ThrowsException_WhenGoodsOffline() + { + // Arrange + var dbContext = CreateInMemoryDbContext(); + var service = CreateCollectionService(dbContext); + + var goods = new Good + { + Id = 1, + Title = "下架商品", + Type = 2, + Status = 0, // 下架 + ShowIs = 0, + Price = 10, + Stock = 5, + ImgUrl = "img.jpg", + ImgUrlDetail = "detail.jpg" + }; + await dbContext.Goods.AddAsync(goods); + await dbContext.SaveChangesAsync(); + + // Act & Assert + await Assert.ThrowsAsync( + () => service.ToggleCollectionAsync(100, 1, 1)); + } + + /// + /// 测试用户隔离 - 只返回当前用户的收藏 + /// Requirements: 6.4 + /// + [Fact] + public async Task GetCollectionList_OnlyReturnsCurrentUserCollections() + { + // Arrange + var dbContext = CreateInMemoryDbContext(); + var service = CreateCollectionService(dbContext); + + // 添加商品 + var goods = new Good + { + Id = 1, + Title = "商品1", + Type = 2, + Status = 1, + ShowIs = 0, + Price = 10, + Stock = 5, + ImgUrl = "img1.jpg", + ImgUrlDetail = "detail1.jpg" + }; + await dbContext.Goods.AddAsync(goods); + + // 添加不同用户的收藏 + var collections = new List + { + new() { Id = 1, UserId = 100, GoodsId = 1, Num = 1, Type = 2, CreatedAt = DateTime.Now }, + new() { Id = 2, UserId = 200, GoodsId = 1, Num = 1, Type = 2, CreatedAt = DateTime.Now } + }; + await dbContext.GoodsCollections.AddRangeAsync(collections); + await dbContext.SaveChangesAsync(); + + // Act + var result = await service.GetCollectionListAsync(100, 0, 1, 10); + + // Assert + Assert.Single(result.Data); + Assert.Equal(1, result.Data[0].GoodsId); + } + + #endregion +} diff --git a/server/C#/HoneyBox/tests/HoneyBox.Tests/Integration/GoodsServiceIntegrationTests.cs b/server/C#/HoneyBox/tests/HoneyBox.Tests/Integration/GoodsServiceIntegrationTests.cs new file mode 100644 index 00000000..bab7e8e3 --- /dev/null +++ b/server/C#/HoneyBox/tests/HoneyBox.Tests/Integration/GoodsServiceIntegrationTests.cs @@ -0,0 +1,637 @@ +using HoneyBox.Core.Interfaces; +using HoneyBox.Core.Services; +using HoneyBox.Model.Data; +using HoneyBox.Model.Entities; +using HoneyBox.Model.Models.Goods; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using Moq; +using Xunit; + +namespace HoneyBox.Tests.Integration; + +/// +/// 商品服务集成测试 +/// 测试完整的商品查询流程 +/// Requirements: 1.1-1.6, 2.1-2.7 +/// +public class GoodsServiceIntegrationTests +{ + private HoneyBoxDbContext CreateInMemoryDbContext() + { + var options = new DbContextOptionsBuilder() + .UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString()) + .Options; + + return new HoneyBoxDbContext(options); + } + + private GoodsService CreateGoodsService(HoneyBoxDbContext dbContext, IGoodsCacheService? cacheService = null) + { + var mockLogger = new Mock>(); + var mockCacheService = cacheService ?? CreateMockCacheService(); + return new GoodsService(dbContext, mockCacheService, mockLogger.Object); + } + + private IGoodsCacheService CreateMockCacheService() + { + var mock = new Mock(); + mock.Setup(x => x.GetJoinCountAsync(It.IsAny())).ReturnsAsync(-1); + mock.Setup(x => x.SetJoinCountAsync(It.IsAny(), It.IsAny())).Returns(Task.CompletedTask); + return mock.Object; + } + + #region 商品列表测试 (Requirements 1.1-1.6) + + /// + /// 测试商品列表查询 - 返回分页商品 + /// Requirements: 1.1 + /// + [Fact] + public async Task GetGoodsList_WithTypeFilter_ReturnsMatchingGoods() + { + // Arrange + var dbContext = CreateInMemoryDbContext(); + var service = CreateGoodsService(dbContext); + + // 添加商品类型 + await dbContext.GoodsTypes.AddAsync(new GoodsType { Value = 2, Name = "无限赏", FlName = "无限赏", CornerText = "无限赏" }); + await dbContext.GoodsTypes.AddAsync(new GoodsType { Value = 6, Name = "全局赏", FlName = "全局赏", CornerText = "全局赏" }); + await dbContext.SaveChangesAsync(); + + // 添加测试商品 + var goods = new List + { + new() { Id = 1, Title = "商品1", Type = 2, Status = 1, ShowIs = 0, Price = 10, Stock = 10, Sort = 1, ImgUrl = "img1.jpg", ImgUrlDetail = "detail1.jpg" }, + new() { Id = 2, Title = "商品2", Type = 2, Status = 1, ShowIs = 0, Price = 20, Stock = 20, Sort = 2, ImgUrl = "img2.jpg", ImgUrlDetail = "detail2.jpg" }, + new() { Id = 3, Title = "商品3", Type = 6, Status = 1, ShowIs = 0, Price = 30, Stock = 30, Sort = 3, ImgUrl = "img3.jpg", ImgUrlDetail = "detail3.jpg" } + }; + await dbContext.Goods.AddRangeAsync(goods); + await dbContext.SaveChangesAsync(); + + // Act - 查询类型2的商品 + var request = new GoodsListRequest { Type = 2, Page = 1, PageSize = 10 }; + var result = await service.GetGoodsListAsync(request, 0); + + // Assert + Assert.NotNull(result); + Assert.Equal(2, result.Total); + Assert.All(result.Data, g => Assert.Equal(2, g.Type)); + } + + /// + /// 测试商品列表查询 - type=-1返回默认类型商品 + /// Requirements: 1.2 + /// + [Fact] + public async Task GetGoodsList_TypeMinusOne_ReturnsDefaultTypeGoods() + { + // Arrange + var dbContext = CreateInMemoryDbContext(); + var service = CreateGoodsService(dbContext); + + // 添加商品类型 + await dbContext.GoodsTypes.AddAsync(new GoodsType { Value = 2, Name = "无限赏", FlName = "无限赏", CornerText = "无限赏" }); + await dbContext.GoodsTypes.AddAsync(new GoodsType { Value = 6, Name = "全局赏", FlName = "全局赏", CornerText = "全局赏" }); + await dbContext.GoodsTypes.AddAsync(new GoodsType { Value = 8, Name = "领主赏", FlName = "领主赏", CornerText = "领主赏" }); + await dbContext.GoodsTypes.AddAsync(new GoodsType { Value = 16, Name = "其他", FlName = "其他", CornerText = "其他" }); + await dbContext.SaveChangesAsync(); + + // 添加测试商品 - 默认类型: 2, 6, 8, 16 + var goods = new List + { + new() { Id = 1, Title = "商品1", Type = 2, Status = 1, ShowIs = 0, Price = 10, Stock = 10, Sort = 1, ImgUrl = "img1.jpg", ImgUrlDetail = "detail1.jpg" }, + new() { Id = 2, Title = "商品2", Type = 6, Status = 1, ShowIs = 0, Price = 20, Stock = 20, Sort = 2, ImgUrl = "img2.jpg", ImgUrlDetail = "detail2.jpg" }, + new() { Id = 3, Title = "商品3", Type = 1, Status = 1, ShowIs = 0, Price = 30, Stock = 30, Sort = 3, ImgUrl = "img3.jpg", ImgUrlDetail = "detail3.jpg" } // 非默认类型 + }; + await dbContext.Goods.AddRangeAsync(goods); + await dbContext.SaveChangesAsync(); + + // Act - type=-1 返回默认类型 + var request = new GoodsListRequest { Type = -1, Page = 1, PageSize = 10 }; + var result = await service.GetGoodsListAsync(request, 0); + + // Assert + Assert.NotNull(result); + Assert.Equal(2, result.Total); // 只有类型2和6的商品 + Assert.All(result.Data, g => Assert.Contains(g.Type, new[] { 2, 6, 8, 16 })); + } + + /// + /// 测试商品列表查询 - 过滤非上架商品 + /// Requirements: 1.4 + /// + [Fact] + public async Task GetGoodsList_FiltersInactiveGoods() + { + // Arrange + var dbContext = CreateInMemoryDbContext(); + var service = CreateGoodsService(dbContext); + + await dbContext.GoodsTypes.AddAsync(new GoodsType { Value = 2, Name = "无限赏", FlName = "无限赏", CornerText = "无限赏" }); + await dbContext.SaveChangesAsync(); + + var goods = new List + { + new() { Id = 1, Title = "上架商品", Type = 2, Status = 1, ShowIs = 0, Price = 10, Stock = 10, Sort = 1, ImgUrl = "img1.jpg", ImgUrlDetail = "detail1.jpg" }, + new() { Id = 2, Title = "下架商品", Type = 2, Status = 0, ShowIs = 0, Price = 20, Stock = 20, Sort = 2, ImgUrl = "img2.jpg", ImgUrlDetail = "detail2.jpg" } + }; + await dbContext.Goods.AddRangeAsync(goods); + await dbContext.SaveChangesAsync(); + + // Act + var request = new GoodsListRequest { Type = 2, Page = 1, PageSize = 10 }; + var result = await service.GetGoodsListAsync(request, 0); + + // Assert + Assert.Single(result.Data); + Assert.Equal("上架商品", result.Data[0].Title); + } + + /// + /// 测试商品列表查询 - 解锁金额过滤 + /// Requirements: 1.5 + /// + [Fact] + public async Task GetGoodsList_FiltersUnlockAmountForAnonymousUser() + { + // Arrange + var dbContext = CreateInMemoryDbContext(); + var service = CreateGoodsService(dbContext); + + await dbContext.GoodsTypes.AddAsync(new GoodsType { Value = 2, Name = "无限赏", FlName = "无限赏", CornerText = "无限赏" }); + await dbContext.SaveChangesAsync(); + + var goods = new List + { + new() { Id = 1, Title = "无门槛商品", Type = 2, Status = 1, ShowIs = 0, Price = 10, Stock = 10, Sort = 1, UnlockAmount = 0, ImgUrl = "img1.jpg", ImgUrlDetail = "detail1.jpg" }, + new() { Id = 2, Title = "有门槛商品", Type = 2, Status = 1, ShowIs = 0, Price = 20, Stock = 20, Sort = 2, UnlockAmount = 100, ImgUrl = "img2.jpg", ImgUrlDetail = "detail2.jpg" } + }; + await dbContext.Goods.AddRangeAsync(goods); + await dbContext.SaveChangesAsync(); + + // Act - 未登录用户 (userId=0) + var request = new GoodsListRequest { Type = 2, Page = 1, PageSize = 10 }; + var result = await service.GetGoodsListAsync(request, 0); + + // Assert - 只能看到无门槛商品 + Assert.Single(result.Data); + Assert.Equal("无门槛商品", result.Data[0].Title); + } + + /// + /// 测试商品列表查询 - 排序正确性 + /// Requirements: 1.6 + /// + [Fact] + public async Task GetGoodsList_SortsBySortDescThenIdDesc() + { + // Arrange + var dbContext = CreateInMemoryDbContext(); + var service = CreateGoodsService(dbContext); + + await dbContext.GoodsTypes.AddAsync(new GoodsType { Value = 2, Name = "无限赏", FlName = "无限赏", CornerText = "无限赏" }); + await dbContext.SaveChangesAsync(); + + var goods = new List + { + new() { Id = 1, Title = "商品1", Type = 2, Status = 1, ShowIs = 0, Price = 10, Stock = 10, Sort = 1, ImgUrl = "img1.jpg", ImgUrlDetail = "detail1.jpg" }, + new() { Id = 2, Title = "商品2", Type = 2, Status = 1, ShowIs = 0, Price = 20, Stock = 20, Sort = 2, ImgUrl = "img2.jpg", ImgUrlDetail = "detail2.jpg" }, + new() { Id = 3, Title = "商品3", Type = 2, Status = 1, ShowIs = 0, Price = 30, Stock = 30, Sort = 2, ImgUrl = "img3.jpg", ImgUrlDetail = "detail3.jpg" } + }; + await dbContext.Goods.AddRangeAsync(goods); + await dbContext.SaveChangesAsync(); + + // Act + var request = new GoodsListRequest { Type = 2, Page = 1, PageSize = 10 }; + var result = await service.GetGoodsListAsync(request, 0); + + // Assert - 按sort DESC, id DESC排序 + Assert.Equal(3, result.Data.Count); + Assert.Equal(3, result.Data[0].Id); // sort=2, id=3 + Assert.Equal(2, result.Data[1].Id); // sort=2, id=2 + Assert.Equal(1, result.Data[2].Id); // sort=1, id=1 + } + + /// + /// 测试商品列表查询 - 分页功能 + /// Requirements: 1.1 + /// + [Fact] + public async Task GetGoodsList_PaginationWorksCorrectly() + { + // Arrange + var dbContext = CreateInMemoryDbContext(); + var service = CreateGoodsService(dbContext); + + await dbContext.GoodsTypes.AddAsync(new GoodsType { Value = 2, Name = "无限赏", FlName = "无限赏", CornerText = "无限赏" }); + await dbContext.SaveChangesAsync(); + + // 添加15条商品 + var goods = Enumerable.Range(1, 15).Select(i => new Good + { + Id = i, + Title = $"商品{i}", + Type = 2, + Status = 1, + ShowIs = 0, + Price = i * 10, + Stock = 10, + Sort = i, + ImgUrl = $"img{i}.jpg", + ImgUrlDetail = $"detail{i}.jpg" + }).ToList(); + await dbContext.Goods.AddRangeAsync(goods); + await dbContext.SaveChangesAsync(); + + // Act - 第一页 + var page1Request = new GoodsListRequest { Type = 2, Page = 1, PageSize = 10 }; + var page1 = await service.GetGoodsListAsync(page1Request, 0); + + // Act - 第二页 + var page2Request = new GoodsListRequest { Type = 2, Page = 2, PageSize = 10 }; + var page2 = await service.GetGoodsListAsync(page2Request, 0); + + // Assert + Assert.Equal(15, page1.Total); + Assert.Equal(2, page1.LastPage); + Assert.Equal(10, page1.Data.Count); + Assert.Equal(5, page2.Data.Count); + } + + #endregion + + #region 商品详情测试 (Requirements 2.1-2.7) + + /// + /// 测试商品详情查询 - 返回完整商品信息 + /// Requirements: 2.1 + /// + [Fact] + public async Task GetGoodsDetail_ReturnsCompleteGoodsInfo() + { + // Arrange + var dbContext = CreateInMemoryDbContext(); + var service = CreateGoodsService(dbContext); + + // 添加商品类型 + await dbContext.GoodsTypes.AddAsync(new GoodsType { Value = 2, Name = "无限赏", FlName = "无限赏", CornerText = "无限赏" }); + await dbContext.SaveChangesAsync(); + + // 添加商品 + var goods = new Good + { + Id = 1, + Title = "测试商品", + Type = 2, + Status = 1, + ShowIs = 0, + Price = 10, + Stock = 5, + SaleStock = 2, + LockIs = 0, + ImgUrl = "img.jpg", + ImgUrlDetail = "detail.jpg", + CouponIs = 1, + CouponPro = 10, + IntegralIs = 1, + RageIs = 0, + LingzhuIs = 0, + DailyXiangou = 0, + QuanjuXiangou = 0 + }; + await dbContext.Goods.AddAsync(goods); + + // 添加奖品等级 + await dbContext.PrizeLevels.AddAsync(new PrizeLevel { Id = 10, Title = "A赏", Color = "#FF0000" }); + await dbContext.SaveChangesAsync(); + + // 添加奖品 + var goodsItem = new GoodsItem + { + Id = 1, + GoodsId = 1, + Num = 1, + Title = "奖品1", + Stock = 10, + SurplusStock = 8, + Price = 100, + ScMoney = 50, + ShangId = 10, + GoodsListId = 0, + ImgUrl = "prize.jpg", + Sort = 1 + }; + await dbContext.GoodsItems.AddAsync(goodsItem); + await dbContext.SaveChangesAsync(); + + // Act + var result = await service.GetGoodsDetailAsync(1, 1, 0); + + // Assert + Assert.NotNull(result); + Assert.NotNull(result.Goods); + Assert.Equal(1, result.Goods.Id); + Assert.Equal("测试商品", result.Goods.Title); + Assert.Equal(2, result.Goods.Type); + Assert.NotNull(result.LockInfo); + Assert.NotNull(result.GoodsList); + Assert.NotNull(result.LimitInfo); + } + + /// + /// 测试商品详情查询 - 自动选择箱号 + /// Requirements: 2.2 + /// + [Fact] + public async Task GetGoodsDetail_AutoSelectsBoxNumber_WhenGoodsNumIsZero() + { + // Arrange + var dbContext = CreateInMemoryDbContext(); + var service = CreateGoodsService(dbContext); + + await dbContext.GoodsTypes.AddAsync(new GoodsType { Value = 2, Name = "无限赏", FlName = "无限赏", CornerText = "无限赏" }); + await dbContext.SaveChangesAsync(); + + var goods = new Good + { + Id = 1, + Title = "测试商品", + Type = 2, + Status = 1, + ShowIs = 0, + Price = 10, + Stock = 3, + ImgUrl = "img.jpg", + ImgUrlDetail = "detail.jpg" + }; + await dbContext.Goods.AddAsync(goods); + + // 添加奖品 - 箱号1无库存,箱号2有库存 + var goodsItems = new List + { + new() { Id = 1, GoodsId = 1, Num = 1, Title = "奖品1", Stock = 10, SurplusStock = 0, ShangId = 10, GoodsListId = 0, ImgUrl = "p1.jpg" }, + new() { Id = 2, GoodsId = 1, Num = 2, Title = "奖品2", Stock = 10, SurplusStock = 5, ShangId = 10, GoodsListId = 0, ImgUrl = "p2.jpg" } + }; + await dbContext.GoodsItems.AddRangeAsync(goodsItems); + await dbContext.PrizeLevels.AddAsync(new PrizeLevel { Id = 10, Title = "A赏" }); + await dbContext.SaveChangesAsync(); + + // Act - goodsNum=0 应自动选择有库存的箱号 + var result = await service.GetGoodsDetailAsync(1, 0, 0); + + // Assert - 应选择箱号2(有库存) + Assert.Equal(2, result.Goods.Num); + } + + /// + /// 测试商品详情查询 - 概率计算 + /// Requirements: 2.3 + /// + [Fact] + public async Task GetGoodsDetail_CalculatesProbabilityCorrectly() + { + // Arrange + var dbContext = CreateInMemoryDbContext(); + var service = CreateGoodsService(dbContext); + + await dbContext.GoodsTypes.AddAsync(new GoodsType { Value = 2, Name = "无限赏", FlName = "无限赏", CornerText = "无限赏" }); + await dbContext.SaveChangesAsync(); + + var goods = new Good + { + Id = 1, + Title = "测试商品", + Type = 2, + Status = 1, + ShowIs = 0, + Price = 10, + Stock = 1, + ImgUrl = "img.jpg", + ImgUrlDetail = "detail.jpg" + }; + await dbContext.Goods.AddAsync(goods); + + // 添加奖品等级 + await dbContext.PrizeLevels.AddAsync(new PrizeLevel { Id = 10, Title = "A赏" }); + await dbContext.PrizeLevels.AddAsync(new PrizeLevel { Id = 11, Title = "B赏" }); + await dbContext.SaveChangesAsync(); + + // 添加奖品 - 总剩余库存=10, A赏剩余2, B赏剩余8 + var goodsItems = new List + { + new() { Id = 1, GoodsId = 1, Num = 1, Title = "A赏奖品", Stock = 5, SurplusStock = 2, ShangId = 10, GoodsListId = 0, ImgUrl = "a.jpg", Sort = 2 }, + new() { Id = 2, GoodsId = 1, Num = 1, Title = "B赏奖品", Stock = 10, SurplusStock = 8, ShangId = 11, GoodsListId = 0, ImgUrl = "b.jpg", Sort = 1 } + }; + await dbContext.GoodsItems.AddRangeAsync(goodsItems); + await dbContext.SaveChangesAsync(); + + // Act + var result = await service.GetGoodsDetailAsync(1, 1, 0); + + // Assert - 概率计算: A赏=2/10*100=20%, B赏=8/10*100=80% + Assert.Equal(2, result.GoodsList.Count); + var aItem = result.GoodsList.First(x => x.ShangId == 10); + var bItem = result.GoodsList.First(x => x.ShangId == 11); + Assert.Contains("20", aItem.Pro); + Assert.Contains("80", bItem.Pro); + } + + /// + /// 测试商品详情查询 - 锁箱信息 + /// Requirements: 2.4 + /// + [Fact] + public async Task GetGoodsDetail_ReturnsLockInfo() + { + // Arrange + var dbContext = CreateInMemoryDbContext(); + var service = CreateGoodsService(dbContext); + + await dbContext.GoodsTypes.AddAsync(new GoodsType { Value = 2, Name = "无限赏", FlName = "无限赏", CornerText = "无限赏" }); + await dbContext.SaveChangesAsync(); + + var goods = new Good + { + Id = 1, + Title = "测试商品", + Type = 2, + Status = 1, + ShowIs = 0, + Price = 10, + Stock = 1, + LockIs = 1, // 支持锁箱 + ImgUrl = "img.jpg", + ImgUrlDetail = "detail.jpg" + }; + await dbContext.Goods.AddAsync(goods); + + // 添加锁箱记录 + var futureTime = DateTimeOffset.UtcNow.AddMinutes(30).ToUnixTimeSeconds(); + var goodsLock = new GoodsLock + { + Id = 1, + GoodsIdNum = "1_1", + UserId = 100, + EndTime = futureTime + }; + await dbContext.GoodsLocks.AddAsync(goodsLock); + + // 添加锁箱用户 + var user = new User + { + Id = 100, + OpenId = "test_openid_100", + Uid = "test_uid_100", + Nickname = "锁箱用户", + HeadImg = "avatar.jpg" + }; + await dbContext.Users.AddAsync(user); + await dbContext.SaveChangesAsync(); + + // Act + var result = await service.GetGoodsDetailAsync(1, 1, 0); + + // Assert + Assert.NotNull(result.LockInfo); + Assert.Equal(1, result.LockInfo.LockIs); + Assert.Equal("锁箱用户", result.LockInfo.GoodsLockUserNickname); + Assert.True(result.LockInfo.GoodsLockSurplusTime > 0); + } + + /// + /// 测试商品详情查询 - 收藏状态 + /// Requirements: 2.6 + /// + [Fact] + public async Task GetGoodsDetail_ReturnsCollectionStatus() + { + // Arrange + var dbContext = CreateInMemoryDbContext(); + var service = CreateGoodsService(dbContext); + + await dbContext.GoodsTypes.AddAsync(new GoodsType { Value = 2, Name = "无限赏", FlName = "无限赏", CornerText = "无限赏" }); + await dbContext.SaveChangesAsync(); + + var goods = new Good + { + Id = 1, + Title = "测试商品", + Type = 2, + Status = 1, + ShowIs = 0, + Price = 10, + Stock = 1, + ImgUrl = "img.jpg", + ImgUrlDetail = "detail.jpg" + }; + await dbContext.Goods.AddAsync(goods); + + // 添加收藏记录 + var collection = new GoodsCollection + { + Id = 1, + UserId = 100, + GoodsId = 1, + Num = 1, + Type = 2 + }; + await dbContext.GoodsCollections.AddAsync(collection); + await dbContext.SaveChangesAsync(); + + // Act - 已收藏用户 + var result = await service.GetGoodsDetailAsync(1, 1, 100); + + // Assert + Assert.Equal(1, result.Goods.CollectionIs); + } + + /// + /// 测试商品详情查询 - 限购信息 + /// Requirements: 2.7 + /// + [Fact] + public async Task GetGoodsDetail_ReturnsLimitInfo() + { + // Arrange + var dbContext = CreateInMemoryDbContext(); + var service = CreateGoodsService(dbContext); + + await dbContext.GoodsTypes.AddAsync(new GoodsType { Value = 2, Name = "无限赏", FlName = "无限赏", CornerText = "无限赏" }); + await dbContext.SaveChangesAsync(); + + var goods = new Good + { + Id = 1, + Title = "测试商品", + Type = 2, + Status = 1, + ShowIs = 0, + Price = 10, + Stock = 1, + DailyXiangou = 5, + QuanjuXiangou = 10, + ImgUrl = "img.jpg", + ImgUrlDetail = "detail.jpg" + }; + await dbContext.Goods.AddAsync(goods); + await dbContext.SaveChangesAsync(); + + // Act + var result = await service.GetGoodsDetailAsync(1, 1, 0); + + // Assert + Assert.NotNull(result.LimitInfo); + Assert.Equal(5, result.LimitInfo.DailyXiangou); + Assert.Equal(10, result.LimitInfo.QuanjuXiangou); + } + + /// + /// 测试商品详情查询 - 商品不存在 + /// Requirements: 2.1 + /// + [Fact] + public async Task GetGoodsDetail_ThrowsException_WhenGoodsNotFound() + { + // Arrange + var dbContext = CreateInMemoryDbContext(); + var service = CreateGoodsService(dbContext); + + // Act & Assert + await Assert.ThrowsAsync( + () => service.GetGoodsDetailAsync(999, 1, 0)); + } + + /// + /// 测试商品详情查询 - 商品已下架 + /// Requirements: 2.1 + /// + [Fact] + public async Task GetGoodsDetail_ThrowsException_WhenGoodsOffline() + { + // Arrange + var dbContext = CreateInMemoryDbContext(); + var service = CreateGoodsService(dbContext); + + var goods = new Good + { + Id = 1, + Title = "下架商品", + Type = 2, + Status = 0, // 下架 + ShowIs = 0, + Price = 10, + Stock = 1, + ImgUrl = "img.jpg", + ImgUrlDetail = "detail.jpg" + }; + await dbContext.Goods.AddAsync(goods); + await dbContext.SaveChangesAsync(); + + // Act & Assert + await Assert.ThrowsAsync( + () => service.GetGoodsDetailAsync(1, 1, 0)); + } + + #endregion +} diff --git a/server/C#/HoneyBox/tests/HoneyBox.Tests/bin/Debug/net10.0/HoneyBox.Core.dll b/server/C#/HoneyBox/tests/HoneyBox.Tests/bin/Debug/net10.0/HoneyBox.Core.dll index 82fbad46..eb1d495c 100644 Binary files a/server/C#/HoneyBox/tests/HoneyBox.Tests/bin/Debug/net10.0/HoneyBox.Core.dll and b/server/C#/HoneyBox/tests/HoneyBox.Tests/bin/Debug/net10.0/HoneyBox.Core.dll differ diff --git a/server/C#/HoneyBox/tests/HoneyBox.Tests/bin/Debug/net10.0/HoneyBox.Core.pdb b/server/C#/HoneyBox/tests/HoneyBox.Tests/bin/Debug/net10.0/HoneyBox.Core.pdb index f37d0d47..5ea93cf6 100644 Binary files a/server/C#/HoneyBox/tests/HoneyBox.Tests/bin/Debug/net10.0/HoneyBox.Core.pdb and b/server/C#/HoneyBox/tests/HoneyBox.Tests/bin/Debug/net10.0/HoneyBox.Core.pdb differ diff --git a/server/C#/HoneyBox/tests/HoneyBox.Tests/bin/Debug/net10.0/HoneyBox.Model.dll b/server/C#/HoneyBox/tests/HoneyBox.Tests/bin/Debug/net10.0/HoneyBox.Model.dll index e8943c95..1b4d5a5e 100644 Binary files a/server/C#/HoneyBox/tests/HoneyBox.Tests/bin/Debug/net10.0/HoneyBox.Model.dll and b/server/C#/HoneyBox/tests/HoneyBox.Tests/bin/Debug/net10.0/HoneyBox.Model.dll differ diff --git a/server/C#/HoneyBox/tests/HoneyBox.Tests/bin/Debug/net10.0/HoneyBox.Model.pdb b/server/C#/HoneyBox/tests/HoneyBox.Tests/bin/Debug/net10.0/HoneyBox.Model.pdb index eee28ec8..e48e680e 100644 Binary files a/server/C#/HoneyBox/tests/HoneyBox.Tests/bin/Debug/net10.0/HoneyBox.Model.pdb and b/server/C#/HoneyBox/tests/HoneyBox.Tests/bin/Debug/net10.0/HoneyBox.Model.pdb differ diff --git a/server/C#/HoneyBox/tests/HoneyBox.Tests/bin/Debug/net10.0/HoneyBox.Tests.dll b/server/C#/HoneyBox/tests/HoneyBox.Tests/bin/Debug/net10.0/HoneyBox.Tests.dll index fb303b97..9ff09f9a 100644 Binary files a/server/C#/HoneyBox/tests/HoneyBox.Tests/bin/Debug/net10.0/HoneyBox.Tests.dll and b/server/C#/HoneyBox/tests/HoneyBox.Tests/bin/Debug/net10.0/HoneyBox.Tests.dll differ diff --git a/server/C#/HoneyBox/tests/HoneyBox.Tests/bin/Debug/net10.0/HoneyBox.Tests.pdb b/server/C#/HoneyBox/tests/HoneyBox.Tests/bin/Debug/net10.0/HoneyBox.Tests.pdb index f38c069d..3db38702 100644 Binary files a/server/C#/HoneyBox/tests/HoneyBox.Tests/bin/Debug/net10.0/HoneyBox.Tests.pdb and b/server/C#/HoneyBox/tests/HoneyBox.Tests/bin/Debug/net10.0/HoneyBox.Tests.pdb differ diff --git a/server/C#/HoneyBox/tests/HoneyBox.Tests/obj/Debug/net10.0/HoneyBox.Tests.AssemblyInfo.cs b/server/C#/HoneyBox/tests/HoneyBox.Tests/obj/Debug/net10.0/HoneyBox.Tests.AssemblyInfo.cs index 28308f7c..6762948d 100644 --- a/server/C#/HoneyBox/tests/HoneyBox.Tests/obj/Debug/net10.0/HoneyBox.Tests.AssemblyInfo.cs +++ b/server/C#/HoneyBox/tests/HoneyBox.Tests/obj/Debug/net10.0/HoneyBox.Tests.AssemblyInfo.cs @@ -13,7 +13,7 @@ using System.Reflection; [assembly: System.Reflection.AssemblyCompanyAttribute("HoneyBox.Tests")] [assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")] [assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")] -[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+21c4bb5c6a0c35c345420f3a29d64fd2922e7427")] +[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+7e00d28ad48928059f0ff514ed804f2cb0626442")] [assembly: System.Reflection.AssemblyProductAttribute("HoneyBox.Tests")] [assembly: System.Reflection.AssemblyTitleAttribute("HoneyBox.Tests")] [assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")] diff --git a/server/C#/HoneyBox/tests/HoneyBox.Tests/obj/Debug/net10.0/HoneyBox.Tests.dll b/server/C#/HoneyBox/tests/HoneyBox.Tests/obj/Debug/net10.0/HoneyBox.Tests.dll index fb303b97..9ff09f9a 100644 Binary files a/server/C#/HoneyBox/tests/HoneyBox.Tests/obj/Debug/net10.0/HoneyBox.Tests.dll and b/server/C#/HoneyBox/tests/HoneyBox.Tests/obj/Debug/net10.0/HoneyBox.Tests.dll differ diff --git a/server/C#/HoneyBox/tests/HoneyBox.Tests/obj/Debug/net10.0/HoneyBox.Tests.pdb b/server/C#/HoneyBox/tests/HoneyBox.Tests/obj/Debug/net10.0/HoneyBox.Tests.pdb index f38c069d..3db38702 100644 Binary files a/server/C#/HoneyBox/tests/HoneyBox.Tests/obj/Debug/net10.0/HoneyBox.Tests.pdb and b/server/C#/HoneyBox/tests/HoneyBox.Tests/obj/Debug/net10.0/HoneyBox.Tests.pdb differ diff --git a/server/C#/HoneyBox/tests/HoneyBox.Tests/obj/Debug/net10.0/ref/HoneyBox.Tests.dll b/server/C#/HoneyBox/tests/HoneyBox.Tests/obj/Debug/net10.0/ref/HoneyBox.Tests.dll index d96f0602..94014df7 100644 Binary files a/server/C#/HoneyBox/tests/HoneyBox.Tests/obj/Debug/net10.0/ref/HoneyBox.Tests.dll and b/server/C#/HoneyBox/tests/HoneyBox.Tests/obj/Debug/net10.0/ref/HoneyBox.Tests.dll differ diff --git a/server/C#/HoneyBox/tests/HoneyBox.Tests/obj/Debug/net10.0/refint/HoneyBox.Tests.dll b/server/C#/HoneyBox/tests/HoneyBox.Tests/obj/Debug/net10.0/refint/HoneyBox.Tests.dll index d96f0602..94014df7 100644 Binary files a/server/C#/HoneyBox/tests/HoneyBox.Tests/obj/Debug/net10.0/refint/HoneyBox.Tests.dll and b/server/C#/HoneyBox/tests/HoneyBox.Tests/obj/Debug/net10.0/refint/HoneyBox.Tests.dll differ