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