阶段4-商品系统
This commit is contained in:
parent
7e00d28ad4
commit
a1e457db06
|
|
@ -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文档已更新
|
||||
- 确保与前端兼容性
|
||||
|
|
|
|||
316
docs/API接口文档.md
316
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": "删除成功"
|
||||
}
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// 收藏控制器
|
||||
/// Requirements: 6.1-6.4
|
||||
/// </summary>
|
||||
[ApiController]
|
||||
[Route("api")]
|
||||
public class CollectionController : ControllerBase
|
||||
{
|
||||
private readonly ICollectionService _collectionService;
|
||||
private readonly ILogger<CollectionController> _logger;
|
||||
|
||||
public CollectionController(
|
||||
ICollectionService collectionService,
|
||||
ILogger<CollectionController> logger)
|
||||
{
|
||||
_collectionService = collectionService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 收藏/取消收藏商品
|
||||
/// POST /api/addCollect
|
||||
/// Requirements: 6.1-6.3
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 切换收藏状态:如果已收藏则取消收藏,如果未收藏则添加收藏
|
||||
/// </remarks>
|
||||
[HttpPost("addCollect")]
|
||||
[Authorize]
|
||||
public async Task<ApiResponse> 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("操作失败");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取收藏列表
|
||||
/// POST /api/listCollect
|
||||
/// Requirements: 6.4
|
||||
/// </summary>
|
||||
[HttpPost("listCollect")]
|
||||
[Authorize]
|
||||
public async Task<ApiResponse<CollectionListResponse>> GetCollectionList([FromBody] CollectionListRequest? request)
|
||||
{
|
||||
var userId = GetCurrentUserId();
|
||||
if (userId == null)
|
||||
{
|
||||
return ApiResponse<CollectionListResponse>.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<CollectionListResponse>.Success(result);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to get collection list: UserId={UserId}", userId);
|
||||
return ApiResponse<CollectionListResponse>.Fail("获取收藏列表失败");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 删除收藏
|
||||
/// POST /api/delCollect
|
||||
/// </summary>
|
||||
[HttpPost("delCollect")]
|
||||
[Authorize]
|
||||
public async Task<ApiResponse> 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
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前登录用户ID
|
||||
/// </summary>
|
||||
private int? GetCurrentUserId()
|
||||
{
|
||||
var userIdClaim = User.FindFirst(ClaimTypes.NameIdentifier);
|
||||
if (userIdClaim == null || !int.TryParse(userIdClaim.Value, out var userId))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return userId;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// 商品控制器
|
||||
/// </summary>
|
||||
[ApiController]
|
||||
[Route("api")]
|
||||
public class GoodsController : ControllerBase
|
||||
{
|
||||
private readonly IGoodsService _goodsService;
|
||||
private readonly IPrizeService _prizeService;
|
||||
private readonly ILogger<GoodsController> _logger;
|
||||
|
||||
public GoodsController(
|
||||
IGoodsService goodsService,
|
||||
IPrizeService prizeService,
|
||||
ILogger<GoodsController> logger)
|
||||
{
|
||||
_goodsService = goodsService;
|
||||
_prizeService = prizeService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取商品列表
|
||||
/// POST /api/goods_list
|
||||
/// Requirements: 1.1-1.6
|
||||
/// </summary>
|
||||
[HttpPost("goods_list")]
|
||||
[Authorize]
|
||||
public async Task<ApiResponse<PageResponse<GoodsListDto>>> GetGoodsList([FromBody] GoodsListRequest? request)
|
||||
{
|
||||
var userId = GetCurrentUserId();
|
||||
if (userId == null)
|
||||
{
|
||||
return ApiResponse<PageResponse<GoodsListDto>>.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<PageResponse<GoodsListDto>>.Success(result);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to get goods list: UserId={UserId}", userId);
|
||||
return ApiResponse<PageResponse<GoodsListDto>>.Fail("获取商品列表失败");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取商品详情
|
||||
/// POST /api/goods_detail
|
||||
/// Requirements: 2.1-2.7
|
||||
/// </summary>
|
||||
[HttpPost("goods_detail")]
|
||||
[Authorize]
|
||||
public async Task<ApiResponse<GoodsDetailResponseDto>> GetGoodsDetail([FromBody] GoodsDetailRequest? request)
|
||||
{
|
||||
var userId = GetCurrentUserId();
|
||||
if (userId == null)
|
||||
{
|
||||
return ApiResponse<GoodsDetailResponseDto>.Unauthorized();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (request == null || request.GoodsId <= 0)
|
||||
{
|
||||
return ApiResponse<GoodsDetailResponseDto>.Fail("商品ID不能为空");
|
||||
}
|
||||
|
||||
var result = await _goodsService.GetGoodsDetailAsync(request.GoodsId, request.GoodsNum, userId.Value);
|
||||
return ApiResponse<GoodsDetailResponseDto>.Success(result);
|
||||
}
|
||||
catch (InvalidOperationException ex)
|
||||
{
|
||||
_logger.LogWarning("Get goods detail failed: GoodsId={GoodsId}, Error={Error}", request?.GoodsId, ex.Message);
|
||||
return ApiResponse<GoodsDetailResponseDto>.Fail(ex.Message);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to get goods detail: GoodsId={GoodsId}", request?.GoodsId);
|
||||
return ApiResponse<GoodsDetailResponseDto>.Fail("获取商品详情失败");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取商品子奖品列表
|
||||
/// POST /api/goods_children
|
||||
/// Requirements: 3.1-3.3
|
||||
/// </summary>
|
||||
[HttpPost("goods_children")]
|
||||
[Authorize]
|
||||
public async Task<ApiResponse<List<GoodsChildrenDto>>> GetGoodsChildren([FromBody] GoodsChildrenRequest? request)
|
||||
{
|
||||
var userId = GetCurrentUserId();
|
||||
if (userId == null)
|
||||
{
|
||||
return ApiResponse<List<GoodsChildrenDto>>.Unauthorized();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (request == null || request.GoodsId <= 0 || request.GoodsListId <= 0)
|
||||
{
|
||||
return ApiResponse<List<GoodsChildrenDto>>.Fail("参数不能为空");
|
||||
}
|
||||
|
||||
var result = await _goodsService.GetGoodsChildrenAsync(request.GoodsId, request.GoodsNum, request.GoodsListId);
|
||||
return ApiResponse<List<GoodsChildrenDto>>.Success(result);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to get goods children: GoodsId={GoodsId}, GoodsListId={GoodsListId}",
|
||||
request?.GoodsId, request?.GoodsListId);
|
||||
return ApiResponse<List<GoodsChildrenDto>>.Fail("获取子奖品列表失败");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取商品扩展配置
|
||||
/// POST /api/goods_extend
|
||||
/// Requirements: 4.1-4.3
|
||||
/// </summary>
|
||||
[HttpPost("goods_extend")]
|
||||
[Authorize]
|
||||
public async Task<ApiResponse<GoodsExtendDto>> GetGoodsExtend([FromBody] GoodsExtendRequest? request)
|
||||
{
|
||||
var userId = GetCurrentUserId();
|
||||
if (userId == null)
|
||||
{
|
||||
return ApiResponse<GoodsExtendDto>.Unauthorized();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (request == null || request.GoodsId <= 0)
|
||||
{
|
||||
return ApiResponse<GoodsExtendDto>.Fail("商品ID不能为空");
|
||||
}
|
||||
|
||||
var result = await _goodsService.GetGoodsExtendAsync(request.GoodsId, request.GoodsType);
|
||||
return ApiResponse<GoodsExtendDto>.Success(result);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to get goods extend: GoodsId={GoodsId}", request?.GoodsId);
|
||||
return ApiResponse<GoodsExtendDto>.Fail("获取商品扩展配置失败");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取箱号列表
|
||||
/// POST /api/goods_num_list
|
||||
/// Requirements: 5.1
|
||||
/// </summary>
|
||||
[HttpPost("goods_num_list")]
|
||||
[Authorize]
|
||||
public async Task<ApiResponse<List<BoxGroupDto>>> GetBoxList([FromBody] BoxListRequest? request)
|
||||
{
|
||||
var userId = GetCurrentUserId();
|
||||
if (userId == null)
|
||||
{
|
||||
return ApiResponse<List<BoxGroupDto>>.Unauthorized();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (request == null || request.GoodsId <= 0)
|
||||
{
|
||||
return ApiResponse<List<BoxGroupDto>>.Fail("商品ID不能为空");
|
||||
}
|
||||
|
||||
var result = await _goodsService.GetBoxListAsync(request.GoodsId);
|
||||
return ApiResponse<List<BoxGroupDto>>.Success(result);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to get box list: GoodsId={GoodsId}", request?.GoodsId);
|
||||
return ApiResponse<List<BoxGroupDto>>.Fail("获取箱号列表失败");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取箱号详情
|
||||
/// POST /api/goods_num_detail
|
||||
/// Requirements: 5.2-5.4
|
||||
/// </summary>
|
||||
[HttpPost("goods_num_detail")]
|
||||
[Authorize]
|
||||
public async Task<ApiResponse<List<BoxDetailDto>>> GetBoxDetail([FromBody] BoxDetailRequest? request)
|
||||
{
|
||||
var userId = GetCurrentUserId();
|
||||
if (userId == null)
|
||||
{
|
||||
return ApiResponse<List<BoxDetailDto>>.Unauthorized();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (request == null || request.GoodsId <= 0)
|
||||
{
|
||||
return ApiResponse<List<BoxDetailDto>>.Fail("商品ID不能为空");
|
||||
}
|
||||
|
||||
var result = await _goodsService.GetBoxDetailAsync(request.GoodsId, request.PageNo, request.Sort);
|
||||
return ApiResponse<List<BoxDetailDto>>.Success(result);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to get box detail: GoodsId={GoodsId}", request?.GoodsId);
|
||||
return ApiResponse<List<BoxDetailDto>>.Fail("获取箱号详情失败");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取奖品数量统计
|
||||
/// POST /api/goods_prize_count
|
||||
/// Requirements: 7.1
|
||||
/// </summary>
|
||||
[HttpPost("goods_prize_count")]
|
||||
[Authorize]
|
||||
public async Task<ApiResponse<PrizeCountResponseDto>> GetPrizeCount([FromBody] PrizeCountRequest? request)
|
||||
{
|
||||
var userId = GetCurrentUserId();
|
||||
if (userId == null)
|
||||
{
|
||||
return ApiResponse<PrizeCountResponseDto>.Unauthorized();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (request == null || request.GoodsId <= 0)
|
||||
{
|
||||
return ApiResponse<PrizeCountResponseDto>.Fail("商品ID不能为空");
|
||||
}
|
||||
|
||||
var result = await _prizeService.GetPrizeCountAsync(request.GoodsId);
|
||||
return ApiResponse<PrizeCountResponseDto>.Success(result);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to get prize count: GoodsId={GoodsId}", request?.GoodsId);
|
||||
return ApiResponse<PrizeCountResponseDto>.Fail("获取奖品统计失败");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取奖品内容
|
||||
/// POST /api/goods_prize_content
|
||||
/// Requirements: 7.2
|
||||
/// </summary>
|
||||
[HttpPost("goods_prize_content")]
|
||||
[Authorize]
|
||||
public async Task<ApiResponse<PrizeContentResponseDto>> GetPrizeContent([FromBody] PrizeContentRequest? request)
|
||||
{
|
||||
var userId = GetCurrentUserId();
|
||||
if (userId == null)
|
||||
{
|
||||
return ApiResponse<PrizeContentResponseDto>.Unauthorized();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (request == null || request.GoodsId <= 0)
|
||||
{
|
||||
return ApiResponse<PrizeContentResponseDto>.Fail("商品ID不能为空");
|
||||
}
|
||||
|
||||
var result = await _prizeService.GetPrizeContentAsync(request.GoodsId, request.Num);
|
||||
return ApiResponse<PrizeContentResponseDto>.Success(result);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to get prize content: GoodsId={GoodsId}", request?.GoodsId);
|
||||
return ApiResponse<PrizeContentResponseDto>.Fail("获取奖品内容失败");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取中奖记录
|
||||
/// POST /api/goods_prize_logs
|
||||
/// Requirements: 8.1-8.4
|
||||
/// </summary>
|
||||
[HttpPost("goods_prize_logs")]
|
||||
[Authorize]
|
||||
public async Task<ApiResponse<PrizeLogsResponseDto>> GetPrizeLogs([FromBody] PrizeLogsRequest? request)
|
||||
{
|
||||
var userId = GetCurrentUserId();
|
||||
if (userId == null)
|
||||
{
|
||||
return ApiResponse<PrizeLogsResponseDto>.Unauthorized();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (request == null || request.GoodsId <= 0)
|
||||
{
|
||||
return ApiResponse<PrizeLogsResponseDto>.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<PrizeLogsResponseDto>.Success(result);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to get prize logs: GoodsId={GoodsId}", request?.GoodsId);
|
||||
return ApiResponse<PrizeLogsResponseDto>.Fail("获取中奖记录失败");
|
||||
}
|
||||
}
|
||||
|
||||
#region Private Helper Methods
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前登录用户ID
|
||||
/// </summary>
|
||||
private int? GetCurrentUserId()
|
||||
{
|
||||
var userIdClaim = User.FindFirst(ClaimTypes.NameIdentifier);
|
||||
if (userIdClaim == null || !int.TryParse(userIdClaim.Value, out var userId))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return userId;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
462
server/C#/HoneyBox/src/HoneyBox.Api/goods-system.http
Normal file
462
server/C#/HoneyBox/src/HoneyBox.Api/goods-system.http
Normal file
|
|
@ -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}}
|
||||
|
||||
{}
|
||||
|
||||
|
|
@ -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")]
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -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":{}}
|
||||
{"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":{}}
|
||||
|
|
@ -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":{}}
|
||||
{"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":{}}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
using HoneyBox.Model.Models;
|
||||
using HoneyBox.Model.Models.Goods;
|
||||
|
||||
namespace HoneyBox.Core.Interfaces;
|
||||
|
||||
/// <summary>
|
||||
/// 收藏服务接口
|
||||
/// </summary>
|
||||
public interface ICollectionService
|
||||
{
|
||||
/// <summary>
|
||||
/// 收藏/取消收藏商品 (PHP addCollect - 切换收藏状态)
|
||||
/// </summary>
|
||||
/// <param name="userId">用户ID</param>
|
||||
/// <param name="goodsId">商品ID</param>
|
||||
/// <param name="goodsNum">箱号</param>
|
||||
/// <returns>操作是否成功</returns>
|
||||
Task<bool> ToggleCollectionAsync(int userId, int goodsId, int goodsNum);
|
||||
|
||||
/// <summary>
|
||||
/// 获取收藏列表 (PHP listCollect)
|
||||
/// </summary>
|
||||
/// <param name="userId">用户ID</param>
|
||||
/// <param name="type">商品类型 (0=全部)</param>
|
||||
/// <param name="page">页码</param>
|
||||
/// <param name="pageSize">每页数量</param>
|
||||
/// <returns>分页收藏列表</returns>
|
||||
Task<CollectionListResponse> GetCollectionListAsync(int userId, int type, int page, int pageSize);
|
||||
|
||||
/// <summary>
|
||||
/// 删除收藏 (PHP delCollect)
|
||||
/// </summary>
|
||||
/// <param name="userId">用户ID</param>
|
||||
/// <param name="collectionId">收藏记录ID</param>
|
||||
/// <returns>操作是否成功</returns>
|
||||
Task<bool> DeleteCollectionAsync(int userId, int collectionId);
|
||||
|
||||
/// <summary>
|
||||
/// 检查是否已收藏
|
||||
/// </summary>
|
||||
/// <param name="userId">用户ID</param>
|
||||
/// <param name="goodsId">商品ID</param>
|
||||
/// <param name="goodsNum">箱号</param>
|
||||
/// <returns>是否已收藏</returns>
|
||||
Task<bool> IsCollectedAsync(int userId, int goodsId, int goodsNum);
|
||||
}
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
namespace HoneyBox.Core.Interfaces;
|
||||
|
||||
/// <summary>
|
||||
/// 商品缓存服务接口
|
||||
/// </summary>
|
||||
public interface IGoodsCacheService
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取商品参与次数
|
||||
/// </summary>
|
||||
/// <param name="goodsId">商品ID</param>
|
||||
/// <returns>参与次数,如果缓存不存在返回-1</returns>
|
||||
Task<int> GetJoinCountAsync(int goodsId);
|
||||
|
||||
/// <summary>
|
||||
/// 设置商品参与次数
|
||||
/// </summary>
|
||||
/// <param name="goodsId">商品ID</param>
|
||||
/// <param name="count">参与次数</param>
|
||||
Task SetJoinCountAsync(int goodsId, int count);
|
||||
|
||||
/// <summary>
|
||||
/// 增加商品参与次数
|
||||
/// </summary>
|
||||
/// <param name="goodsId">商品ID</param>
|
||||
/// <returns>增加后的参与次数</returns>
|
||||
Task<int> IncrementJoinCountAsync(int goodsId);
|
||||
|
||||
/// <summary>
|
||||
/// 清除商品缓存
|
||||
/// </summary>
|
||||
/// <param name="goodsId">商品ID</param>
|
||||
Task InvalidateGoodsCacheAsync(int goodsId);
|
||||
|
||||
/// <summary>
|
||||
/// 获取商品列表缓存
|
||||
/// </summary>
|
||||
/// <param name="cacheKey">缓存键</param>
|
||||
/// <returns>缓存的商品列表JSON,如果不存在返回null</returns>
|
||||
Task<string?> GetGoodsListCacheAsync(string cacheKey);
|
||||
|
||||
/// <summary>
|
||||
/// 设置商品列表缓存
|
||||
/// </summary>
|
||||
/// <param name="cacheKey">缓存键</param>
|
||||
/// <param name="data">商品列表JSON</param>
|
||||
/// <param name="expiry">过期时间</param>
|
||||
Task SetGoodsListCacheAsync(string cacheKey, string data, TimeSpan expiry);
|
||||
|
||||
/// <summary>
|
||||
/// 获取商品详情缓存
|
||||
/// </summary>
|
||||
/// <param name="goodsId">商品ID</param>
|
||||
/// <param name="goodsNum">箱号</param>
|
||||
/// <returns>缓存的商品详情JSON,如果不存在返回null</returns>
|
||||
Task<string?> GetGoodsDetailCacheAsync(int goodsId, int goodsNum);
|
||||
|
||||
/// <summary>
|
||||
/// 设置商品详情缓存
|
||||
/// </summary>
|
||||
/// <param name="goodsId">商品ID</param>
|
||||
/// <param name="goodsNum">箱号</param>
|
||||
/// <param name="data">商品详情JSON</param>
|
||||
/// <param name="expiry">过期时间</param>
|
||||
Task SetGoodsDetailCacheAsync(int goodsId, int goodsNum, string data, TimeSpan expiry);
|
||||
}
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
using HoneyBox.Model.Models;
|
||||
using HoneyBox.Model.Models.Goods;
|
||||
|
||||
namespace HoneyBox.Core.Interfaces;
|
||||
|
||||
/// <summary>
|
||||
/// 商品服务接口
|
||||
/// </summary>
|
||||
public interface IGoodsService
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取商品列表
|
||||
/// </summary>
|
||||
/// <param name="request">查询请求</param>
|
||||
/// <param name="userId">用户ID</param>
|
||||
/// <returns>分页商品列表</returns>
|
||||
Task<PageResponse<GoodsListDto>> GetGoodsListAsync(GoodsListRequest request, int userId);
|
||||
|
||||
/// <summary>
|
||||
/// 获取商品详情
|
||||
/// </summary>
|
||||
/// <param name="goodsId">商品ID</param>
|
||||
/// <param name="goodsNum">箱号 (0表示自动选择)</param>
|
||||
/// <param name="userId">用户ID</param>
|
||||
/// <returns>商品详情</returns>
|
||||
Task<GoodsDetailResponseDto> GetGoodsDetailAsync(int goodsId, int goodsNum, int userId);
|
||||
|
||||
/// <summary>
|
||||
/// 获取商品子奖品列表
|
||||
/// </summary>
|
||||
/// <param name="goodsId">商品ID</param>
|
||||
/// <param name="goodsNum">箱号</param>
|
||||
/// <param name="goodsListId">奖品列表ID</param>
|
||||
/// <returns>子奖品列表</returns>
|
||||
Task<List<GoodsChildrenDto>> GetGoodsChildrenAsync(int goodsId, int goodsNum, int goodsListId);
|
||||
|
||||
/// <summary>
|
||||
/// 获取商品扩展配置
|
||||
/// </summary>
|
||||
/// <param name="goodsId">商品ID</param>
|
||||
/// <param name="goodsType">商品类型</param>
|
||||
/// <returns>扩展配置</returns>
|
||||
Task<GoodsExtendDto> GetGoodsExtendAsync(int goodsId, int goodsType);
|
||||
|
||||
/// <summary>
|
||||
/// 获取箱号列表
|
||||
/// </summary>
|
||||
/// <param name="goodsId">商品ID</param>
|
||||
/// <returns>箱号分组列表</returns>
|
||||
Task<List<BoxGroupDto>> GetBoxListAsync(int goodsId);
|
||||
|
||||
/// <summary>
|
||||
/// 获取箱号详情
|
||||
/// </summary>
|
||||
/// <param name="goodsId">商品ID</param>
|
||||
/// <param name="pageNo">页码</param>
|
||||
/// <param name="sort">排序方式: 0=箱号升序, 1=箱号降序, 2=余量降序</param>
|
||||
/// <returns>箱号详情列表</returns>
|
||||
Task<List<BoxDetailDto>> GetBoxDetailAsync(int goodsId, int pageNo, int sort);
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
using HoneyBox.Model.Models.Goods;
|
||||
|
||||
namespace HoneyBox.Core.Interfaces;
|
||||
|
||||
/// <summary>
|
||||
/// 奖品服务接口
|
||||
/// </summary>
|
||||
public interface IPrizeService
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取奖品数量统计
|
||||
/// </summary>
|
||||
/// <param name="goodsId">商品ID</param>
|
||||
/// <returns>奖品数量统计</returns>
|
||||
Task<PrizeCountResponseDto> GetPrizeCountAsync(int goodsId);
|
||||
|
||||
/// <summary>
|
||||
/// 获取奖品内容
|
||||
/// </summary>
|
||||
/// <param name="goodsId">商品ID</param>
|
||||
/// <param name="num">箱号</param>
|
||||
/// <returns>奖品内容</returns>
|
||||
Task<PrizeContentResponseDto> GetPrizeContentAsync(int goodsId, int num);
|
||||
|
||||
/// <summary>
|
||||
/// 获取中奖记录
|
||||
/// </summary>
|
||||
/// <param name="goodsId">商品ID</param>
|
||||
/// <param name="goodsNum">箱号</param>
|
||||
/// <param name="shangId">赏品分类ID (0表示全部)</param>
|
||||
/// <param name="page">页码</param>
|
||||
/// <param name="pageSize">每页数量</param>
|
||||
/// <returns>中奖记录</returns>
|
||||
Task<PrizeLogsResponseDto> GetPrizeLogsAsync(int goodsId, int goodsNum, int shangId, int page, int pageSize);
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// 收藏服务实现
|
||||
/// </summary>
|
||||
public class CollectionService : ICollectionService
|
||||
{
|
||||
private readonly HoneyBoxDbContext _dbContext;
|
||||
private readonly ILogger<CollectionService> _logger;
|
||||
private readonly IRedisService _redisService;
|
||||
|
||||
// 奖品统计ID范围 (与PHP保持一致)
|
||||
private static readonly int[] ShangPrizeIdRange = { 10, 33 };
|
||||
|
||||
public CollectionService(
|
||||
HoneyBoxDbContext dbContext,
|
||||
ILogger<CollectionService> logger,
|
||||
IRedisService redisService)
|
||||
{
|
||||
_dbContext = dbContext;
|
||||
_logger = logger;
|
||||
_redisService = redisService;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<bool> 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;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<CollectionListResponse> 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<CollectionDto>(),
|
||||
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<string, (int Stock, int SurplusStock)>();
|
||||
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<CollectionDto>();
|
||||
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
|
||||
};
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<bool> 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;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<bool> 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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 格式化图片URL
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,114 @@
|
|||
using HoneyBox.Core.Interfaces;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace HoneyBox.Core.Services;
|
||||
|
||||
/// <summary>
|
||||
/// 商品缓存服务实现
|
||||
/// </summary>
|
||||
public class GoodsCacheService : IGoodsCacheService
|
||||
{
|
||||
private readonly IRedisService _redisService;
|
||||
private readonly ILogger<GoodsCacheService> _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<GoodsCacheService> logger)
|
||||
{
|
||||
_redisService = redisService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<int> 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;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task SetJoinCountAsync(int goodsId, int count)
|
||||
{
|
||||
var key = $"{JoinCountKeyPrefix}{goodsId}";
|
||||
await _redisService.SetStringAsync(key, count.ToString(), JoinCountExpiry);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<int> 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;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task InvalidateGoodsCacheAsync(int goodsId)
|
||||
{
|
||||
// 清除参与次数缓存
|
||||
var joinCountKey = $"{JoinCountKeyPrefix}{goodsId}";
|
||||
await _redisService.DeleteAsync(joinCountKey);
|
||||
|
||||
// 清除商品详情缓存 - 由于箱号可能有多个,这里清除所有可能的箱号缓存
|
||||
// 注意:实际生产环境中可能需要使用Redis的SCAN命令来批量删除
|
||||
// 这里简化处理,只清除常见的箱号范围(0-100)
|
||||
var deleteTasks = new List<Task>();
|
||||
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);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<string?> GetGoodsListCacheAsync(string cacheKey)
|
||||
{
|
||||
var key = $"{GoodsListKeyPrefix}{cacheKey}";
|
||||
return await _redisService.GetStringAsync(key);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task SetGoodsListCacheAsync(string cacheKey, string data, TimeSpan expiry)
|
||||
{
|
||||
var key = $"{GoodsListKeyPrefix}{cacheKey}";
|
||||
await _redisService.SetStringAsync(key, data, expiry);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<string?> GetGoodsDetailCacheAsync(int goodsId, int goodsNum)
|
||||
{
|
||||
var key = $"{GoodsDetailKeyPrefix}{goodsId}_{goodsNum}";
|
||||
return await _redisService.GetStringAsync(key);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task SetGoodsDetailCacheAsync(int goodsId, int goodsNum, string data, TimeSpan expiry)
|
||||
{
|
||||
var key = $"{GoodsDetailKeyPrefix}{goodsId}_{goodsNum}";
|
||||
await _redisService.SetStringAsync(key, data, expiry);
|
||||
}
|
||||
}
|
||||
1136
server/C#/HoneyBox/src/HoneyBox.Core/Services/GoodsService.cs
Normal file
1136
server/C#/HoneyBox/src/HoneyBox.Core/Services/GoodsService.cs
Normal file
File diff suppressed because it is too large
Load Diff
421
server/C#/HoneyBox/src/HoneyBox.Core/Services/PrizeService.cs
Normal file
421
server/C#/HoneyBox/src/HoneyBox.Core/Services/PrizeService.cs
Normal file
|
|
@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// 奖品服务实现
|
||||
/// </summary>
|
||||
public class PrizeService : IPrizeService
|
||||
{
|
||||
private readonly HoneyBoxDbContext _dbContext;
|
||||
private readonly ILogger<PrizeService> _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<PrizeService> logger)
|
||||
{
|
||||
_dbContext = dbContext;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<PrizeCountResponseDto> 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)
|
||||
};
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<PrizeContentResponseDto> 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<GoodsListItemDto>();
|
||||
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
|
||||
};
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<PrizeLogsResponseDto> 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<CategoryDto>
|
||||
{
|
||||
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
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 格式化图片URL
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 移除小数末尾的零
|
||||
/// </summary>
|
||||
private static string RemoveTrailingZeros(decimal value)
|
||||
{
|
||||
return value.ToString("0.##");
|
||||
}
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -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")]
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -116,5 +116,41 @@ public class ServiceModule : Module
|
|||
var logger = c.Resolve<ILogger<WelfareService>>();
|
||||
return new WelfareService(dbContext, logger);
|
||||
}).As<IWelfareService>().InstancePerLifetimeScope();
|
||||
|
||||
// ========== 商品系统服务注册 ==========
|
||||
|
||||
// 注册商品缓存服务
|
||||
builder.Register(c =>
|
||||
{
|
||||
var redisService = c.Resolve<IRedisService>();
|
||||
var logger = c.Resolve<ILogger<GoodsCacheService>>();
|
||||
return new GoodsCacheService(redisService, logger);
|
||||
}).As<IGoodsCacheService>().InstancePerLifetimeScope();
|
||||
|
||||
// 注册商品服务
|
||||
builder.Register(c =>
|
||||
{
|
||||
var dbContext = c.Resolve<HoneyBoxDbContext>();
|
||||
var cacheService = c.Resolve<IGoodsCacheService>();
|
||||
var logger = c.Resolve<ILogger<GoodsService>>();
|
||||
return new GoodsService(dbContext, cacheService, logger);
|
||||
}).As<IGoodsService>().InstancePerLifetimeScope();
|
||||
|
||||
// 注册收藏服务
|
||||
builder.Register(c =>
|
||||
{
|
||||
var dbContext = c.Resolve<HoneyBoxDbContext>();
|
||||
var logger = c.Resolve<ILogger<CollectionService>>();
|
||||
var redisService = c.Resolve<IRedisService>();
|
||||
return new CollectionService(dbContext, logger, redisService);
|
||||
}).As<ICollectionService>().InstancePerLifetimeScope();
|
||||
|
||||
// 注册奖品服务
|
||||
builder.Register(c =>
|
||||
{
|
||||
var dbContext = c.Resolve<HoneyBoxDbContext>();
|
||||
var logger = c.Resolve<ILogger<PrizeService>>();
|
||||
return new PrizeService(dbContext, logger);
|
||||
}).As<IPrizeService>().InstancePerLifetimeScope();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -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")]
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -94,6 +94,10 @@ public partial class HoneyBoxDbContext : DbContext
|
|||
|
||||
public virtual DbSet<RedeemCode> RedeemCodes { get; set; }
|
||||
|
||||
public virtual DbSet<GoodsCollection> GoodsCollections { get; set; }
|
||||
|
||||
public virtual DbSet<GoodsLock> 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<GoodsCollection>(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<GoodsLock>(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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,39 @@
|
|||
using System;
|
||||
|
||||
namespace HoneyBox.Model.Entities;
|
||||
|
||||
/// <summary>
|
||||
/// 商品收藏表,存储用户收藏的商品信息
|
||||
/// </summary>
|
||||
public partial class GoodsCollection
|
||||
{
|
||||
/// <summary>
|
||||
/// 收藏ID
|
||||
/// </summary>
|
||||
public int Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 用户ID
|
||||
/// </summary>
|
||||
public int UserId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 商品ID
|
||||
/// </summary>
|
||||
public int GoodsId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 箱号
|
||||
/// </summary>
|
||||
public int Num { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 商品类型 1-一番赏 2-无限赏 3-擂台赏 4-抽卡机 5-积分赏 6-全局赏 7-福利盲盒 8-领主赏 9-连击赏
|
||||
/// </summary>
|
||||
public byte Type { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 添加时间
|
||||
/// </summary>
|
||||
public DateTime CreatedAt { get; set; }
|
||||
}
|
||||
34
server/C#/HoneyBox/src/HoneyBox.Model/Entities/GoodsLock.cs
Normal file
34
server/C#/HoneyBox/src/HoneyBox.Model/Entities/GoodsLock.cs
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
using System;
|
||||
|
||||
namespace HoneyBox.Model.Entities;
|
||||
|
||||
/// <summary>
|
||||
/// 商品锁箱信息表,存储盲盒锁箱状态
|
||||
/// </summary>
|
||||
public partial class GoodsLock
|
||||
{
|
||||
/// <summary>
|
||||
/// 锁箱ID
|
||||
/// </summary>
|
||||
public int Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 用户ID
|
||||
/// </summary>
|
||||
public int UserId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 商品ID和箱号组合 (格式: goods_id_num)
|
||||
/// </summary>
|
||||
public string GoodsIdNum { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// 过期时间 (Unix时间戳)
|
||||
/// </summary>
|
||||
public long EndTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 更新时间 (Unix时间戳)
|
||||
/// </summary>
|
||||
public long UpdateTime { get; set; }
|
||||
}
|
||||
|
|
@ -1,3 +1,5 @@
|
|||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace HoneyBox.Model.Models.Asset;
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -8,16 +10,19 @@ public class AssetRecordRequest
|
|||
/// <summary>
|
||||
/// 类型过滤 (0=全部)
|
||||
/// </summary>
|
||||
[JsonPropertyName("type")]
|
||||
public int Type { get; set; } = 0;
|
||||
|
||||
/// <summary>
|
||||
/// 页码
|
||||
/// </summary>
|
||||
[JsonPropertyName("page")]
|
||||
public int Page { get; set; } = 1;
|
||||
|
||||
/// <summary>
|
||||
/// 每页数量
|
||||
/// </summary>
|
||||
[JsonPropertyName("limit")]
|
||||
public int Limit { get; set; } = 15;
|
||||
}
|
||||
|
||||
|
|
@ -29,41 +34,48 @@ public class AssetRecordDto
|
|||
/// <summary>
|
||||
/// 变动金额 (带正负号)
|
||||
/// </summary>
|
||||
[JsonPropertyName("change_money")]
|
||||
public string ChangeMoney { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 变动说明
|
||||
/// </summary>
|
||||
[JsonPropertyName("content")]
|
||||
public string Content { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 添加时间 (格式: Y-m-d H:i:s)
|
||||
/// </summary>
|
||||
[JsonPropertyName("addtime")]
|
||||
public string AddTime { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 资产记录分页响应
|
||||
/// 资产记录分页响应 (兼容PHP API格式)
|
||||
/// </summary>
|
||||
public class AssetRecordPageResponse
|
||||
{
|
||||
/// <summary>
|
||||
/// 记录列表
|
||||
/// </summary>
|
||||
[JsonPropertyName("list")]
|
||||
public List<AssetRecordDto> List { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// 最后一页页码
|
||||
/// </summary>
|
||||
[JsonPropertyName("last_page")]
|
||||
public int LastPage { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 当前页码
|
||||
/// </summary>
|
||||
[JsonPropertyName("current_page")]
|
||||
public int CurrentPage { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 总记录数
|
||||
/// </summary>
|
||||
[JsonPropertyName("total")]
|
||||
public int Total { get; set; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace HoneyBox.Model.Models;
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -8,16 +10,18 @@ public class PageRequest
|
|||
/// <summary>
|
||||
/// 页码,从1开始
|
||||
/// </summary>
|
||||
[JsonPropertyName("page")]
|
||||
public int Page { get; set; } = 1;
|
||||
|
||||
/// <summary>
|
||||
/// 每页数量
|
||||
/// </summary>
|
||||
[JsonPropertyName("page_size")]
|
||||
public int PageSize { get; set; } = 10;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 分页响应基类
|
||||
/// 分页响应基类 (兼容PHP API格式)
|
||||
/// </summary>
|
||||
/// <typeparam name="T">数据类型</typeparam>
|
||||
public class PageResponse<T>
|
||||
|
|
@ -25,27 +29,32 @@ public class PageResponse<T>
|
|||
/// <summary>
|
||||
/// 数据列表
|
||||
/// </summary>
|
||||
public List<T> List { get; set; } = new();
|
||||
[JsonPropertyName("data")]
|
||||
public List<T> Data { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// 最后一页页码 (兼容PHP API)
|
||||
/// </summary>
|
||||
[JsonPropertyName("last_page")]
|
||||
public int LastPage { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 总数量
|
||||
/// </summary>
|
||||
[JsonPropertyName("total")]
|
||||
public int Total { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 当前页码
|
||||
/// </summary>
|
||||
[JsonPropertyName("page")]
|
||||
public int Page { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 每页数量
|
||||
/// </summary>
|
||||
[JsonPropertyName("page_size")]
|
||||
public int PageSize { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 总页数
|
||||
/// </summary>
|
||||
public int TotalPages => PageSize > 0 ? (int)Math.Ceiling((double)Total / PageSize) : 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -56,6 +65,7 @@ public class IdRequest
|
|||
/// <summary>
|
||||
/// ID
|
||||
/// </summary>
|
||||
[JsonPropertyName("id")]
|
||||
public int Id { get; set; }
|
||||
}
|
||||
|
||||
|
|
@ -67,5 +77,6 @@ public class IdsRequest
|
|||
/// <summary>
|
||||
/// ID列表
|
||||
/// </summary>
|
||||
[JsonPropertyName("ids")]
|
||||
public List<int> Ids { get; set; } = new();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
/// <summary>
|
||||
/// 商品列表请求
|
||||
|
|
@ -13,110 +16,903 @@ public class GoodsListRequest : PageRequest
|
|||
public int? CategoryId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 商品类型
|
||||
/// 商品类型 (-1表示全部)
|
||||
/// </summary>
|
||||
public byte? Type { get; set; }
|
||||
[JsonPropertyName("type")]
|
||||
public int Type { get; set; } = -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 商品列表项响应
|
||||
/// 商品详情请求
|
||||
/// </summary>
|
||||
public class GoodsListItemResponse
|
||||
public class GoodsDetailRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// 商品ID
|
||||
/// </summary>
|
||||
[JsonPropertyName("goods_id")]
|
||||
public int GoodsId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 箱号 (0表示自动选择)
|
||||
/// </summary>
|
||||
[JsonPropertyName("goods_num")]
|
||||
public int GoodsNum { get; set; } = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 商品子奖品请求
|
||||
/// </summary>
|
||||
public class GoodsChildrenRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// 商品ID
|
||||
/// </summary>
|
||||
[JsonPropertyName("goods_id")]
|
||||
public int GoodsId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 箱号
|
||||
/// </summary>
|
||||
[JsonPropertyName("goods_num")]
|
||||
public int GoodsNum { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 奖品列表ID
|
||||
/// </summary>
|
||||
[JsonPropertyName("goods_list_id")]
|
||||
public int GoodsListId { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 商品扩展配置请求
|
||||
/// </summary>
|
||||
public class GoodsExtendRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// 商品ID
|
||||
/// </summary>
|
||||
[JsonPropertyName("goods_id")]
|
||||
public int GoodsId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 商品类型
|
||||
/// </summary>
|
||||
[JsonPropertyName("goods_type")]
|
||||
public int GoodsType { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 箱号列表请求
|
||||
/// </summary>
|
||||
public class BoxListRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// 商品ID
|
||||
/// </summary>
|
||||
[JsonPropertyName("goods_id")]
|
||||
public int GoodsId { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 箱号详情请求
|
||||
/// </summary>
|
||||
public class BoxDetailRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// 商品ID
|
||||
/// </summary>
|
||||
[JsonPropertyName("goods_id")]
|
||||
public int GoodsId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 页码 (每页10个箱号)
|
||||
/// </summary>
|
||||
[JsonPropertyName("page_no")]
|
||||
public int PageNo { get; set; } = 0;
|
||||
|
||||
/// <summary>
|
||||
/// 排序方式: 0=箱号升序, 1=箱号降序, 2=余量降序
|
||||
/// </summary>
|
||||
[JsonPropertyName("sort")]
|
||||
public int Sort { get; set; } = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 奖品数量统计请求
|
||||
/// </summary>
|
||||
public class PrizeCountRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// 商品ID
|
||||
/// </summary>
|
||||
[JsonPropertyName("goods_id")]
|
||||
public int GoodsId { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 奖品内容请求
|
||||
/// </summary>
|
||||
public class PrizeContentRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// 商品ID
|
||||
/// </summary>
|
||||
[JsonPropertyName("goods_id")]
|
||||
public int GoodsId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 箱号
|
||||
/// </summary>
|
||||
[JsonPropertyName("num")]
|
||||
public int Num { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 中奖记录请求
|
||||
/// </summary>
|
||||
public class PrizeLogsRequest : PageRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// 商品ID
|
||||
/// </summary>
|
||||
[JsonPropertyName("goods_id")]
|
||||
public int GoodsId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 箱号
|
||||
/// </summary>
|
||||
[JsonPropertyName("goods_num")]
|
||||
public int GoodsNum { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 赏品分类ID (0表示全部)
|
||||
/// </summary>
|
||||
[JsonPropertyName("shang_id")]
|
||||
public int ShangId { get; set; } = 0;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Response/DTO Models
|
||||
|
||||
/// <summary>
|
||||
/// 商品列表项DTO (兼容PHP API snake_case格式)
|
||||
/// </summary>
|
||||
public class GoodsListDto
|
||||
{
|
||||
/// <summary>
|
||||
/// 商品ID
|
||||
/// </summary>
|
||||
[JsonPropertyName("id")]
|
||||
public int Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 商品标题
|
||||
/// </summary>
|
||||
[JsonPropertyName("title")]
|
||||
public string Title { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 商品图片URL
|
||||
/// </summary>
|
||||
[JsonPropertyName("img_url")]
|
||||
public string ImgUrl { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 商品价格
|
||||
/// </summary>
|
||||
public decimal Price { get; set; }
|
||||
[JsonPropertyName("price")]
|
||||
public string Price { get; set; } = "0";
|
||||
|
||||
/// <summary>
|
||||
/// 商品类型
|
||||
/// </summary>
|
||||
[JsonPropertyName("type")]
|
||||
public int Type { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 类型文字
|
||||
/// </summary>
|
||||
[JsonPropertyName("type_text")]
|
||||
public string TypeText { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 总库存
|
||||
/// </summary>
|
||||
[JsonPropertyName("stock")]
|
||||
public int Stock { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 已售库存
|
||||
/// </summary>
|
||||
[JsonPropertyName("sale_stock")]
|
||||
public int SaleStock { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 状态
|
||||
/// </summary>
|
||||
[JsonPropertyName("status")]
|
||||
public int Status { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否锁定
|
||||
/// </summary>
|
||||
[JsonPropertyName("lock_is")]
|
||||
public int LockIs { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否首折
|
||||
/// </summary>
|
||||
[JsonPropertyName("is_shou_zhe")]
|
||||
public int IsShouZhe { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否新品
|
||||
/// </summary>
|
||||
[JsonPropertyName("new_is")]
|
||||
public int NewIs { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 参与次数
|
||||
/// </summary>
|
||||
[JsonPropertyName("join_count")]
|
||||
public int JoinCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 需要抽奖次数
|
||||
/// </summary>
|
||||
[JsonPropertyName("need_draw_num")]
|
||||
public int NeedDrawNum { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 显示价格
|
||||
/// </summary>
|
||||
[JsonPropertyName("show_price")]
|
||||
public string? ShowPrice { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 商品类型
|
||||
/// 角标文字
|
||||
/// </summary>
|
||||
public byte Type { get; set; }
|
||||
[JsonPropertyName("corner_text")]
|
||||
public string? CornerText { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 商品信息DTO
|
||||
/// </summary>
|
||||
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; }
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 商品详情响应
|
||||
/// </summary>
|
||||
public class GoodsDetailResponse
|
||||
public class GoodsDetailResponseDto
|
||||
{
|
||||
/// <summary>
|
||||
/// 商品ID
|
||||
/// 商品信息
|
||||
/// </summary>
|
||||
public int Id { get; set; }
|
||||
[JsonPropertyName("goods")]
|
||||
public GoodsInfoDto Goods { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// 商品标题
|
||||
/// 锁箱信息
|
||||
/// </summary>
|
||||
public string Title { get; set; } = string.Empty;
|
||||
[JsonPropertyName("lock_info")]
|
||||
public LockInfoDto LockInfo { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// 商品图片URL
|
||||
/// 参与用户头像列表
|
||||
/// </summary>
|
||||
public string ImgUrl { get; set; } = string.Empty;
|
||||
[JsonPropertyName("join_user")]
|
||||
public List<string> JoinUser { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// 商品详情图片URL
|
||||
/// 参与次数
|
||||
/// </summary>
|
||||
public string ImgUrlDetail { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 商品价格
|
||||
/// </summary>
|
||||
public decimal Price { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 商品描述
|
||||
/// </summary>
|
||||
public string? GoodsDescribe { get; set; }
|
||||
[JsonPropertyName("join_count")]
|
||||
public int JoinCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 奖品列表
|
||||
/// </summary>
|
||||
public List<GoodsPrizeItemResponse> Prizes { get; set; } = new();
|
||||
[JsonPropertyName("goodslist")]
|
||||
public List<GoodsListItemDto> GoodsList { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// 限购信息
|
||||
/// </summary>
|
||||
[JsonPropertyName("limit_info")]
|
||||
public LimitInfoDto LimitInfo { get; set; } = new();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 商品奖品项响应
|
||||
/// 锁箱信息DTO
|
||||
/// </summary>
|
||||
public class GoodsPrizeItemResponse
|
||||
public class LockInfoDto
|
||||
{
|
||||
/// <summary>
|
||||
/// 奖品ID
|
||||
/// 是否锁定
|
||||
/// </summary>
|
||||
[JsonPropertyName("lock_is")]
|
||||
public int LockIs { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 锁定用户昵称
|
||||
/// </summary>
|
||||
[JsonPropertyName("goods_lock_user_nickname")]
|
||||
public string? GoodsLockUserNickname { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 锁定用户头像
|
||||
/// </summary>
|
||||
[JsonPropertyName("goods_lock_user_headimg")]
|
||||
public string? GoodsLockUserHeadimg { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 锁箱剩余时间 (Unix时间戳)
|
||||
/// </summary>
|
||||
[JsonPropertyName("goods_lock_surplus_time")]
|
||||
public long GoodsLockSurplusTime { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 限购信息DTO
|
||||
/// </summary>
|
||||
public class LimitInfoDto
|
||||
{
|
||||
/// <summary>
|
||||
/// 每日限购数量
|
||||
/// </summary>
|
||||
[JsonPropertyName("daily_xiangou")]
|
||||
public int DailyXiangou { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 今日已购数量
|
||||
/// </summary>
|
||||
[JsonPropertyName("daily_bought")]
|
||||
public int DailyBought { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 今日剩余可购数量
|
||||
/// </summary>
|
||||
[JsonPropertyName("daily_remaining")]
|
||||
public int DailyRemaining { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 全局限购数量
|
||||
/// </summary>
|
||||
[JsonPropertyName("quanju_xiangou")]
|
||||
public int QuanjuXiangou { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 全局已购数量
|
||||
/// </summary>
|
||||
[JsonPropertyName("global_bought")]
|
||||
public int GlobalBought { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 全局剩余可购数量
|
||||
/// </summary>
|
||||
[JsonPropertyName("global_remaining")]
|
||||
public int GlobalRemaining { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 奖品列表项DTO
|
||||
/// </summary>
|
||||
public class GoodsListItemDto
|
||||
{
|
||||
[JsonPropertyName("id")]
|
||||
public int Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 奖品名称
|
||||
/// </summary>
|
||||
public string Name { get; set; } = string.Empty;
|
||||
[JsonPropertyName("shang_id")]
|
||||
public int ShangId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 奖品图片
|
||||
/// </summary>
|
||||
[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;
|
||||
|
||||
/// <summary>
|
||||
/// 奖品等级
|
||||
/// </summary>
|
||||
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";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 赏品等级信息DTO
|
||||
/// </summary>
|
||||
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; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 子奖品DTO
|
||||
/// </summary>
|
||||
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; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 商品扩展配置DTO
|
||||
/// </summary>
|
||||
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; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 箱号分组DTO
|
||||
/// </summary>
|
||||
public class BoxGroupDto
|
||||
{
|
||||
/// <summary>
|
||||
/// 显示标题,如 "1-10"
|
||||
/// </summary>
|
||||
[JsonPropertyName("title")]
|
||||
public string Title { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 页码(从0开始)
|
||||
/// </summary>
|
||||
[JsonPropertyName("page_no")]
|
||||
public int PageNo { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 箱号详情DTO
|
||||
/// </summary>
|
||||
public class BoxDetailDto
|
||||
{
|
||||
[JsonPropertyName("num")]
|
||||
public int Num { get; set; }
|
||||
|
||||
[JsonPropertyName("surplus_all_stock")]
|
||||
public int SurplusAllStock { get; set; }
|
||||
|
||||
[JsonPropertyName("goodslist")]
|
||||
public List<BoxGoodsListDto> GoodsList { get; set; } = new();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 箱号商品列表DTO
|
||||
/// </summary>
|
||||
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
|
||||
|
||||
/// <summary>
|
||||
/// 收藏请求
|
||||
/// </summary>
|
||||
public class CollectionRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// 商品ID
|
||||
/// </summary>
|
||||
[JsonPropertyName("goods_id")]
|
||||
public int GoodsId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 箱号
|
||||
/// </summary>
|
||||
[JsonPropertyName("goods_num")]
|
||||
public int GoodsNum { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 操作类型: add=收藏, remove=取消收藏
|
||||
/// </summary>
|
||||
[JsonPropertyName("action")]
|
||||
public string Action { get; set; } = "add";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 收藏列表请求
|
||||
/// </summary>
|
||||
public class CollectionListRequest : PageRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// 商品类型 (0=全部)
|
||||
/// </summary>
|
||||
[JsonPropertyName("type")]
|
||||
public int Type { get; set; } = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 收藏列表响应 (兼容PHP API格式)
|
||||
/// </summary>
|
||||
public class CollectionListResponse
|
||||
{
|
||||
[JsonPropertyName("data")]
|
||||
public List<CollectionDto> Data { get; set; } = new();
|
||||
|
||||
[JsonPropertyName("last_page")]
|
||||
public int LastPage { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 收藏DTO (兼容PHP API格式)
|
||||
/// </summary>
|
||||
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; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 删除收藏请求
|
||||
/// </summary>
|
||||
public class DeleteCollectionRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// 收藏记录ID
|
||||
/// </summary>
|
||||
[JsonPropertyName("id")]
|
||||
public int Id { get; set; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Prize Statistics Models
|
||||
|
||||
/// <summary>
|
||||
/// 奖品数量统计响应
|
||||
/// </summary>
|
||||
public class PrizeCountResponseDto
|
||||
{
|
||||
[JsonPropertyName("list")]
|
||||
public List<PrizeCountItemDto> List { get; set; } = new();
|
||||
|
||||
[JsonPropertyName("total")]
|
||||
public int Total { get; set; }
|
||||
|
||||
[JsonPropertyName("surplus")]
|
||||
public int Surplus { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 奖品数量统计项DTO
|
||||
/// </summary>
|
||||
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; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 奖品内容响应
|
||||
/// </summary>
|
||||
public class PrizeContentResponseDto
|
||||
{
|
||||
[JsonPropertyName("list")]
|
||||
public List<GoodsListItemDto> List { get; set; } = new();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Prize Logs Models
|
||||
|
||||
/// <summary>
|
||||
/// 中奖记录响应
|
||||
/// </summary>
|
||||
public class PrizeLogsResponseDto
|
||||
{
|
||||
[JsonPropertyName("category")]
|
||||
public List<CategoryDto> Category { get; set; } = new();
|
||||
|
||||
[JsonPropertyName("data")]
|
||||
public List<PrizeLogDto> Data { get; set; } = new();
|
||||
|
||||
[JsonPropertyName("last_page")]
|
||||
public int LastPage { get; set; }
|
||||
|
||||
[JsonPropertyName("total")]
|
||||
public int Total { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 分类DTO - 用于中奖记录筛选
|
||||
/// </summary>
|
||||
public class CategoryDto
|
||||
{
|
||||
[JsonPropertyName("shang_id")]
|
||||
public int ShangId { get; set; }
|
||||
|
||||
[JsonPropertyName("shang_title")]
|
||||
public string ShangTitle { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 中奖记录DTO - 匹配PHP API响应格式
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 中奖记录用户信息DTO
|
||||
/// </summary>
|
||||
public class PrizeLogUserDto
|
||||
{
|
||||
[JsonPropertyName("nickname")]
|
||||
public string Nickname { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("headimg")]
|
||||
public string HeadImg { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
namespace HoneyBox.Model.Models.Prize;
|
||||
|
||||
using System.Text.Json.Serialization;
|
||||
using HoneyBox.Model.Models;
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -10,11 +11,13 @@ public class DrawPrizeRequest
|
|||
/// <summary>
|
||||
/// 商品ID
|
||||
/// </summary>
|
||||
[JsonPropertyName("goods_id")]
|
||||
public int GoodsId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 抽奖次数
|
||||
/// </summary>
|
||||
[JsonPropertyName("times")]
|
||||
public int Times { get; set; } = 1;
|
||||
}
|
||||
|
||||
|
|
@ -26,16 +29,19 @@ public class DrawPrizeResponse
|
|||
/// <summary>
|
||||
/// 中奖奖品列表
|
||||
/// </summary>
|
||||
[JsonPropertyName("prizes")]
|
||||
public List<PrizeResultItem> Prizes { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// 消耗金额
|
||||
/// </summary>
|
||||
[JsonPropertyName("cost_amount")]
|
||||
public decimal CostAmount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 剩余余额
|
||||
/// </summary>
|
||||
[JsonPropertyName("remaining_balance")]
|
||||
public decimal RemainingBalance { get; set; }
|
||||
}
|
||||
|
||||
|
|
@ -47,26 +53,31 @@ public class PrizeResultItem
|
|||
/// <summary>
|
||||
/// 奖品ID
|
||||
/// </summary>
|
||||
[JsonPropertyName("id")]
|
||||
public int Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 奖品名称
|
||||
/// </summary>
|
||||
[JsonPropertyName("name")]
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 奖品图片
|
||||
/// </summary>
|
||||
[JsonPropertyName("img_url")]
|
||||
public string ImgUrl { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 奖品等级
|
||||
/// </summary>
|
||||
[JsonPropertyName("level")]
|
||||
public int Level { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 奖品价值
|
||||
/// </summary>
|
||||
[JsonPropertyName("value")]
|
||||
public decimal Value { get; set; }
|
||||
}
|
||||
|
||||
|
|
@ -78,6 +89,7 @@ public class PrizeRecordListRequest : PageRequest
|
|||
/// <summary>
|
||||
/// 商品ID
|
||||
/// </summary>
|
||||
[JsonPropertyName("goods_id")]
|
||||
public int? GoodsId { get; set; }
|
||||
}
|
||||
|
||||
|
|
@ -89,20 +101,24 @@ public class PrizeRecordResponse
|
|||
/// <summary>
|
||||
/// 记录ID
|
||||
/// </summary>
|
||||
[JsonPropertyName("id")]
|
||||
public int Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 奖品名称
|
||||
/// </summary>
|
||||
[JsonPropertyName("prize_name")]
|
||||
public string PrizeName { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 奖品图片
|
||||
/// </summary>
|
||||
[JsonPropertyName("prize_img")]
|
||||
public string PrizeImg { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 中奖时间
|
||||
/// </summary>
|
||||
[JsonPropertyName("created_at")]
|
||||
public DateTime CreatedAt { get; set; }
|
||||
}
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
|
|
@ -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")]
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// 收藏服务集成测试
|
||||
/// 测试收藏/取消收藏流程
|
||||
/// Requirements: 6.1-6.4
|
||||
/// </summary>
|
||||
public class CollectionServiceIntegrationTests
|
||||
{
|
||||
private HoneyBoxDbContext CreateInMemoryDbContext()
|
||||
{
|
||||
var options = new DbContextOptionsBuilder<HoneyBoxDbContext>()
|
||||
.UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
|
||||
.Options;
|
||||
|
||||
return new HoneyBoxDbContext(options);
|
||||
}
|
||||
|
||||
private CollectionService CreateCollectionService(HoneyBoxDbContext dbContext)
|
||||
{
|
||||
var mockLogger = new Mock<ILogger<CollectionService>>();
|
||||
var mockRedisService = new Mock<IRedisService>();
|
||||
mockRedisService.Setup(x => x.DeleteAsync(It.IsAny<string>())).Returns(Task.CompletedTask);
|
||||
return new CollectionService(dbContext, mockLogger.Object, mockRedisService.Object);
|
||||
}
|
||||
|
||||
#region 收藏功能测试 (Requirements 6.1-6.4)
|
||||
|
||||
/// <summary>
|
||||
/// 测试添加收藏
|
||||
/// Requirements: 6.1
|
||||
/// </summary>
|
||||
[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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试取消收藏
|
||||
/// Requirements: 6.2
|
||||
/// </summary>
|
||||
[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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试收藏往返一致性 - 添加后再取消
|
||||
/// Requirements: 6.1, 6.2
|
||||
/// </summary>
|
||||
[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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试收藏列表查询
|
||||
/// Requirements: 6.4
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task GetCollectionList_ReturnsUserCollections()
|
||||
{
|
||||
// Arrange
|
||||
var dbContext = CreateInMemoryDbContext();
|
||||
var service = CreateCollectionService(dbContext);
|
||||
|
||||
// 添加商品
|
||||
var goods = new List<Good>
|
||||
{
|
||||
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<GoodsCollection>
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试收藏列表查询 - 按类型过滤
|
||||
/// Requirements: 6.4
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task GetCollectionList_FiltersByType()
|
||||
{
|
||||
// Arrange
|
||||
var dbContext = CreateInMemoryDbContext();
|
||||
var service = CreateCollectionService(dbContext);
|
||||
|
||||
// 添加商品
|
||||
var goods = new List<Good>
|
||||
{
|
||||
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<GoodsCollection>
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试收藏列表查询 - 分页
|
||||
/// Requirements: 6.4
|
||||
/// </summary>
|
||||
[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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试检查收藏状态
|
||||
/// Requirements: 6.1
|
||||
/// </summary>
|
||||
[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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试删除收藏
|
||||
/// Requirements: 6.2
|
||||
/// </summary>
|
||||
[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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试删除不存在的收藏
|
||||
/// Requirements: 6.2
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task DeleteCollection_ThrowsException_WhenNotFound()
|
||||
{
|
||||
// Arrange
|
||||
var dbContext = CreateInMemoryDbContext();
|
||||
var service = CreateCollectionService(dbContext);
|
||||
|
||||
// Act & Assert
|
||||
await Assert.ThrowsAsync<InvalidOperationException>(
|
||||
() => service.DeleteCollectionAsync(100, 999));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试收藏商品不存在
|
||||
/// Requirements: 6.1
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task ToggleCollection_ThrowsException_WhenGoodsNotFound()
|
||||
{
|
||||
// Arrange
|
||||
var dbContext = CreateInMemoryDbContext();
|
||||
var service = CreateCollectionService(dbContext);
|
||||
|
||||
// Act & Assert
|
||||
await Assert.ThrowsAsync<InvalidOperationException>(
|
||||
() => service.ToggleCollectionAsync(100, 999, 1));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试收藏已下架商品
|
||||
/// Requirements: 6.1
|
||||
/// </summary>
|
||||
[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<InvalidOperationException>(
|
||||
() => service.ToggleCollectionAsync(100, 1, 1));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试用户隔离 - 只返回当前用户的收藏
|
||||
/// Requirements: 6.4
|
||||
/// </summary>
|
||||
[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<GoodsCollection>
|
||||
{
|
||||
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
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// 商品服务集成测试
|
||||
/// 测试完整的商品查询流程
|
||||
/// Requirements: 1.1-1.6, 2.1-2.7
|
||||
/// </summary>
|
||||
public class GoodsServiceIntegrationTests
|
||||
{
|
||||
private HoneyBoxDbContext CreateInMemoryDbContext()
|
||||
{
|
||||
var options = new DbContextOptionsBuilder<HoneyBoxDbContext>()
|
||||
.UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
|
||||
.Options;
|
||||
|
||||
return new HoneyBoxDbContext(options);
|
||||
}
|
||||
|
||||
private GoodsService CreateGoodsService(HoneyBoxDbContext dbContext, IGoodsCacheService? cacheService = null)
|
||||
{
|
||||
var mockLogger = new Mock<ILogger<GoodsService>>();
|
||||
var mockCacheService = cacheService ?? CreateMockCacheService();
|
||||
return new GoodsService(dbContext, mockCacheService, mockLogger.Object);
|
||||
}
|
||||
|
||||
private IGoodsCacheService CreateMockCacheService()
|
||||
{
|
||||
var mock = new Mock<IGoodsCacheService>();
|
||||
mock.Setup(x => x.GetJoinCountAsync(It.IsAny<int>())).ReturnsAsync(-1);
|
||||
mock.Setup(x => x.SetJoinCountAsync(It.IsAny<int>(), It.IsAny<int>())).Returns(Task.CompletedTask);
|
||||
return mock.Object;
|
||||
}
|
||||
|
||||
#region 商品列表测试 (Requirements 1.1-1.6)
|
||||
|
||||
/// <summary>
|
||||
/// 测试商品列表查询 - 返回分页商品
|
||||
/// Requirements: 1.1
|
||||
/// </summary>
|
||||
[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<Good>
|
||||
{
|
||||
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));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试商品列表查询 - type=-1返回默认类型商品
|
||||
/// Requirements: 1.2
|
||||
/// </summary>
|
||||
[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<Good>
|
||||
{
|
||||
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 }));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试商品列表查询 - 过滤非上架商品
|
||||
/// Requirements: 1.4
|
||||
/// </summary>
|
||||
[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<Good>
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试商品列表查询 - 解锁金额过滤
|
||||
/// Requirements: 1.5
|
||||
/// </summary>
|
||||
[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<Good>
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试商品列表查询 - 排序正确性
|
||||
/// Requirements: 1.6
|
||||
/// </summary>
|
||||
[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<Good>
|
||||
{
|
||||
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
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试商品列表查询 - 分页功能
|
||||
/// Requirements: 1.1
|
||||
/// </summary>
|
||||
[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)
|
||||
|
||||
/// <summary>
|
||||
/// 测试商品详情查询 - 返回完整商品信息
|
||||
/// Requirements: 2.1
|
||||
/// </summary>
|
||||
[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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试商品详情查询 - 自动选择箱号
|
||||
/// Requirements: 2.2
|
||||
/// </summary>
|
||||
[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<GoodsItem>
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试商品详情查询 - 概率计算
|
||||
/// Requirements: 2.3
|
||||
/// </summary>
|
||||
[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<GoodsItem>
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试商品详情查询 - 锁箱信息
|
||||
/// Requirements: 2.4
|
||||
/// </summary>
|
||||
[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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试商品详情查询 - 收藏状态
|
||||
/// Requirements: 2.6
|
||||
/// </summary>
|
||||
[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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试商品详情查询 - 限购信息
|
||||
/// Requirements: 2.7
|
||||
/// </summary>
|
||||
[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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试商品详情查询 - 商品不存在
|
||||
/// Requirements: 2.1
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task GetGoodsDetail_ThrowsException_WhenGoodsNotFound()
|
||||
{
|
||||
// Arrange
|
||||
var dbContext = CreateInMemoryDbContext();
|
||||
var service = CreateGoodsService(dbContext);
|
||||
|
||||
// Act & Assert
|
||||
await Assert.ThrowsAsync<InvalidOperationException>(
|
||||
() => service.GetGoodsDetailAsync(999, 1, 0));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试商品详情查询 - 商品已下架
|
||||
/// Requirements: 2.1
|
||||
/// </summary>
|
||||
[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<InvalidOperationException>(
|
||||
() => service.GetGoodsDetailAsync(1, 1, 0));
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -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")]
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loading…
Reference in New Issue
Block a user