diff --git a/.kiro/specs/float-ball-migration/design.md b/.kiro/specs/float-ball-migration/design.md new file mode 100644 index 00000000..fe05709c --- /dev/null +++ b/.kiro/specs/float-ball-migration/design.md @@ -0,0 +1,290 @@ +# Design Document: Float Ball Migration + +## Overview + +本设计文档描述悬浮球功能从 PHP 后端迁移到 .NET 10 后端的技术方案。包括数据库表设计、数据迁移脚本、API 接口实现和 Entity Framework 配置。 + +悬浮球是首页显示的可点击浮动图标,支持两种交互方式: +1. **展示图片** (type=1): 点击后弹出图片弹窗 +2. **跳转页面** (type=2): 点击后跳转到指定页面 + +## Architecture + +``` +┌─────────────────────────────────────────────────────────────┐ +│ Frontend (UniApp) │ +│ FloatBall.vue Component │ +└─────────────────────────────────────────────────────────────┘ + │ + │ GET /api/getFloatBall + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ HoneyBox.Api Layer │ +│ ConfigController.cs │ +│ [AllowAnonymous] endpoint │ +└─────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ HoneyBox.Core Layer │ +│ IFloatBallService.cs │ +│ FloatBallService.cs │ +└─────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ HoneyBox.Model Layer │ +│ FloatBallConfig.cs (Entity) │ +│ FloatBallResponse.cs (DTO) │ +└─────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ SQL Server │ +│ float_ball_configs table │ +└─────────────────────────────────────────────────────────────┘ +``` + +## Components and Interfaces + +### 1. Database Table: float_ball_configs + +```sql +CREATE TABLE float_ball_configs ( + id INT IDENTITY(1,1) PRIMARY KEY, + status TINYINT NOT NULL DEFAULT 0, -- 状态: 0关闭 1开启 + type TINYINT NOT NULL DEFAULT 1, -- 类型: 1展示图片 2跳转页面 + image NVARCHAR(255) NOT NULL DEFAULT '', -- 悬浮球图片URL + link_url NVARCHAR(255) NOT NULL DEFAULT '', -- 跳转链接 + position_x NVARCHAR(30) NOT NULL DEFAULT '', -- X轴位置 + position_y NVARCHAR(30) NOT NULL DEFAULT '', -- Y轴位置 + width NVARCHAR(30) NOT NULL DEFAULT '', -- 宽度 + height NVARCHAR(30) NOT NULL DEFAULT '', -- 高度 + effect TINYINT NOT NULL DEFAULT 0, -- 特效: 0无 1缩放动画 + title NVARCHAR(255) NULL, -- 标题 + image_details NVARCHAR(255) NULL, -- 详情图片URL + image_bj NVARCHAR(255) NULL, -- 背景图片URL + image_details_x NVARCHAR(255) NULL, -- 详情图片X偏移 + image_details_y NVARCHAR(255) NULL, -- 详情图片Y偏移 + image_details_w NVARCHAR(255) NULL, -- 详情图片宽度 + image_details_h NVARCHAR(255) NULL, -- 详情图片高度 + created_at DATETIME2 NOT NULL DEFAULT GETDATE(), + updated_at DATETIME2 NOT NULL DEFAULT GETDATE() +); +``` + +### 2. Entity Class: FloatBallConfig + +```csharp +namespace HoneyBox.Model.Entities; + +public class FloatBallConfig +{ + public int Id { get; set; } + public byte Status { get; set; } + public byte Type { get; set; } + public string Image { get; set; } = string.Empty; + public string LinkUrl { get; set; } = string.Empty; + public string PositionX { get; set; } = string.Empty; + public string PositionY { get; set; } = string.Empty; + public string Width { get; set; } = string.Empty; + public string Height { get; set; } = string.Empty; + public byte Effect { get; set; } + public string? Title { get; set; } + public string? ImageDetails { get; set; } + public string? ImageBj { get; set; } + public string? ImageDetailsX { get; set; } + public string? ImageDetailsY { get; set; } + public string? ImageDetailsW { get; set; } + public string? ImageDetailsH { get; set; } + public DateTime CreatedAt { get; set; } + public DateTime UpdatedAt { get; set; } +} +``` + +### 3. Response DTO: FloatBallResponse + +```csharp +namespace HoneyBox.Model.Models.FloatBall; + +public class FloatBallResponse +{ + public int Id { get; set; } + public int Type { get; set; } + public string Image { get; set; } = string.Empty; + public string LinkUrl { get; set; } = string.Empty; + public string PositionX { get; set; } = string.Empty; + public string PositionY { get; set; } = string.Empty; + public string Width { get; set; } = string.Empty; + public string Height { get; set; } = string.Empty; + public int Effect { get; set; } + public string? Title { get; set; } + public string? ImageDetails { get; set; } + public string? ImageBj { get; set; } + public string? ImageDetailsX { get; set; } + public string? ImageDetailsY { get; set; } + public string? ImageDetailsW { get; set; } + public string? ImageDetailsH { get; set; } +} +``` + +### 4. Service Interface: IFloatBallService + +```csharp +namespace HoneyBox.Core.Interfaces; + +public interface IFloatBallService +{ + Task> GetEnabledFloatBallsAsync(); +} +``` + +### 5. API Endpoint + +``` +GET /api/getFloatBall +Authorization: None (AllowAnonymous) +Response: ApiResponse> +``` + +## Data Models + +### MySQL Source Table Schema (float_ball_config) + +| Column | Type | Description | +|--------|------|-------------| +| id | int(11) | 主键ID | +| status | tinyint(1) | 状态: 0关闭 1开启 | +| type | tinyint(1) | 类型: 1展示图片 2跳转页面 | +| image | varchar(255) | 悬浮球图片URL | +| link_url | varchar(255) | 跳转链接 | +| position_x | varchar(30) | X轴位置 (如: -0%, 10px) | +| position_y | varchar(30) | Y轴位置 (如: 70vh, 21vh) | +| width | varchar(30) | 宽度 (如: 150rpx, 52rpx) | +| height | varchar(30) | 高度 (如: 165rpx, 120rpx) | +| effect | tinyint(1) | 特效: 0无 1缩放动画 | +| create_time | int(11) | 创建时间 (Unix时间戳) | +| update_time | int(11) | 更新时间 (Unix时间戳) | +| title | varchar(255) | 标题 | +| image_details | varchar(255) | 详情图片URL | +| image_bj | varchar(255) | 背景图片URL | +| image_details_x | varchar(255) | 详情图片X偏移 | +| image_details_y | varchar(255) | 详情图片Y偏移 | +| image_details_w | varchar(255) | 详情图片宽度 | +| image_details_h | varchar(255) | 详情图片高度 | + +### Data Migration Mapping + +| MySQL Column | SQL Server Column | Transformation | +|--------------|-------------------|----------------| +| id | id | 保持原值 (IDENTITY_INSERT ON) | +| status | status | 直接映射 | +| type | type | 直接映射 | +| image | image | 直接映射 | +| link_url | link_url | 直接映射 | +| position_x | position_x | 直接映射 | +| position_y | position_y | 直接映射 | +| width | width | 直接映射 | +| height | height | 直接映射 | +| effect | effect | 直接映射 | +| title | title | 直接映射 | +| image_details | image_details | 直接映射 | +| image_bj | image_bj | 直接映射 | +| image_details_x | image_details_x | 直接映射 | +| image_details_y | image_details_y | 直接映射 | +| image_details_w | image_details_w | 直接映射 | +| image_details_h | image_details_h | 直接映射 | +| create_time | created_at | Unix时间戳 → DATETIME2 | +| update_time | updated_at | Unix时间戳 → DATETIME2 | + +## Correctness Properties + +*A property is a characteristic or behavior that should hold true across all valid executions of a system—essentially, a formal statement about what the system should do. Properties serve as the bridge between human-readable specifications and machine-verifiable correctness guarantees.* + +### Property 1: Data Migration Record Count Consistency + +*For any* migration execution, the number of records in SQL Server `float_ball_configs` table after migration SHALL equal the number of records in MySQL `float_ball_config` table. + +**Validates: Requirements 2.4** + +### Property 2: Data Migration ID Preservation + +*For any* record migrated from MySQL to SQL Server, the `id` value in the target table SHALL equal the `id` value in the source table. + +**Validates: Requirements 2.3** + +### Property 3: Timestamp Transformation Validity + +*For any* Unix timestamp value from MySQL, the transformed DATETIME2 value in SQL Server SHALL represent the same point in time. + +**Validates: Requirements 2.2** + +### Property 4: API Returns Only Enabled Configurations + +*For any* GET request to `/api/getFloatBall`, all returned configurations SHALL have status equal to 1 (enabled) in the database. + +**Validates: Requirements 3.1, 3.2** + +### Property 5: API Response Field Completeness + +*For any* configuration returned by the API, the response SHALL contain all required fields (id, type, image, link_url, position_x, position_y, width, height, effect, title, image_details, image_bj, image_details_x, image_details_y, image_details_w, image_details_h) and SHALL NOT contain status, created_at, updated_at fields. + +**Validates: Requirements 3.3, 3.4** + +### Property 6: API Response Format Consistency + +*For any* successful API response, the format SHALL be `{ "status": 1, "msg": "...", "data": [...] }` with status equal to 1. + +**Validates: Requirements 5.1, 5.2** + +### Property 7: Image URL Preservation + +*For any* configuration with image URLs (image, image_details, image_bj), the API response SHALL return the URLs unchanged from the database values. + +**Validates: Requirements 5.4** + +### Property 8: Incremental Migration Idempotence + +*For any* migration script execution, running the migration twice SHALL result in the same final state (no duplicate records). + +**Validates: Requirements 2.5** + +## Error Handling + +### Migration Script Errors + +1. **Connection Failure**: Log error and exit with non-zero code +2. **Single Record Insert Failure**: Log error, continue with remaining records +3. **Batch Insert Failure**: Fall back to single record inserts + +### API Errors + +1. **Database Connection Error**: Return `{ "status": 0, "msg": "获取悬浮球配置失败", "data": null }` +2. **Unexpected Exception**: Log error, return generic error message + +## Testing Strategy + +### Unit Tests + +- Test FloatBallService.GetEnabledFloatBallsAsync() returns only enabled configs +- Test response DTO mapping excludes status, created_at, updated_at +- Test empty result when no enabled configs exist + +### Property-Based Tests + +使用 xUnit + FsCheck 进行属性测试: + +1. **Property 1**: 验证迁移后记录数一致性 +2. **Property 4**: 验证 API 只返回启用的配置 +3. **Property 8**: 验证迁移脚本幂等性 + +### Integration Tests + +- Test full API endpoint `/api/getFloatBall` returns correct format +- Test migration script with test database + +### Test Configuration + +- Property tests: 最少 100 次迭代 +- 使用 FsCheck 生成随机测试数据 +- 标签格式: **Feature: float-ball-migration, Property {number}: {property_text}** diff --git a/.kiro/specs/float-ball-migration/requirements.md b/.kiro/specs/float-ball-migration/requirements.md new file mode 100644 index 00000000..c689fb12 --- /dev/null +++ b/.kiro/specs/float-ball-migration/requirements.md @@ -0,0 +1,73 @@ +# Requirements Document + +## Introduction + +悬浮球功能迁移 - 将悬浮球配置管理功能从 PHP (ThinkPHP) 后端迁移到 .NET 10 后端。包括数据库表迁移、数据迁移和 API 接口迁移。悬浮球是首页显示的可点击浮动图标,支持展示图片弹窗或跳转页面两种交互方式。 + +## Glossary + +- **Float_Ball_System**: 悬浮球系统,负责管理和展示首页悬浮球配置 +- **Float_Ball_Config**: 悬浮球配置实体,存储单个悬浮球的所有配置信息 +- **Float_Ball_Service**: 悬浮球业务服务,处理悬浮球相关的业务逻辑 +- **Migration_Script**: 数据迁移脚本,负责将 MySQL 数据迁移到 SQL Server + +## Requirements + +### Requirement 1: 数据库表迁移 + +**User Story:** As a developer, I want to create the float_ball_configs table in SQL Server, so that the system can store float ball configuration data. + +#### Acceptance Criteria + +1. THE Float_Ball_System SHALL create a `float_ball_configs` table in SQL Server with all required columns +2. THE Float_Ball_System SHALL support the following columns: id, status, type, image, link_url, position_x, position_y, width, height, effect, title, image_details, image_bj, image_details_x, image_details_y, image_details_w, image_details_h, created_at, updated_at +3. THE Float_Ball_System SHALL use appropriate SQL Server data types matching the original MySQL schema +4. THE Float_Ball_System SHALL set id as primary key with auto-increment + +### Requirement 2: 数据迁移 + +**User Story:** As a developer, I want to migrate existing float ball data from MySQL to SQL Server, so that the new system has all historical configurations. + +#### Acceptance Criteria + +1. THE Migration_Script SHALL read all records from MySQL `float_ball_config` table +2. THE Migration_Script SHALL transform Unix timestamps to SQL Server DATETIME2 format +3. THE Migration_Script SHALL insert all records into SQL Server `float_ball_configs` table preserving original IDs +4. THE Migration_Script SHALL verify record count consistency after migration +5. THE Migration_Script SHALL support incremental migration (skip already migrated records) +6. IF migration fails for a record, THEN THE Migration_Script SHALL log the error and continue with remaining records + +### Requirement 3: 获取悬浮球配置接口 + +**User Story:** As a frontend developer, I want to call the getFloatBall API, so that I can display float balls on the homepage. + +#### Acceptance Criteria + +1. WHEN a GET request is made to `/api/getFloatBall`, THE Float_Ball_System SHALL return all enabled float ball configurations +2. THE Float_Ball_System SHALL only return configurations where status equals 1 (enabled) +3. THE Float_Ball_System SHALL return the following fields for each configuration: id, type, image, link_url, position_x, position_y, width, height, effect, title, image_details, image_bj, image_details_x, image_details_y, image_details_w, image_details_h +4. THE Float_Ball_System SHALL NOT return status, created_at, updated_at fields in the response +5. THE Float_Ball_System SHALL return an empty array if no enabled configurations exist +6. THE Float_Ball_System SHALL NOT require authentication for this endpoint + +### Requirement 4: Entity Framework 实体配置 + +**User Story:** As a developer, I want to create the FloatBallConfig entity class, so that Entity Framework can map the database table correctly. + +#### Acceptance Criteria + +1. THE Float_Ball_System SHALL create a `FloatBallConfig` entity class in HoneyBox.Model +2. THE Float_Ball_System SHALL configure proper column mappings using Fluent API or Data Annotations +3. THE Float_Ball_System SHALL register the entity in HoneyBoxDbContext +4. THE Float_Ball_System SHALL support nullable fields for optional columns (title, image_details, image_bj, etc.) + +### Requirement 5: 响应格式兼容性 + +**User Story:** As a frontend developer, I want the API response format to be compatible with the existing frontend, so that no frontend changes are required. + +#### Acceptance Criteria + +1. THE Float_Ball_System SHALL return response in format: `{ "status": 1, "msg": "获取悬浮球配置成功", "data": [...] }` +2. THE Float_Ball_System SHALL return status 1 for successful requests +3. THE Float_Ball_System SHALL return status 0 for failed requests with error message +4. WHEN returning image URLs, THE Float_Ball_System SHALL preserve the original URL format (already contains full CDN path) diff --git a/.kiro/specs/float-ball-migration/tasks.md b/.kiro/specs/float-ball-migration/tasks.md new file mode 100644 index 00000000..fd2729e0 --- /dev/null +++ b/.kiro/specs/float-ball-migration/tasks.md @@ -0,0 +1,84 @@ +# Implementation Plan: Float Ball Migration + +## Overview + +将悬浮球功能从 PHP 后端迁移到 .NET 10 后端,包括数据库表创建、数据迁移脚本和 API 接口实现。 + +## Tasks + +- [x] 1. 创建数据库表和 Entity 配置 + - [x] 1.1 创建 SQL Server 表 `float_ball_configs` + - 在 SQL Server 中执行建表语句 + - 包含所有必要字段和约束 + - _Requirements: 1.1, 1.2, 1.3, 1.4_ + + - [x] 1.2 创建 FloatBallConfig 实体类 + - 在 `HoneyBox.Model/Entities/` 目录创建 `FloatBallConfig.cs` + - 定义所有属性和数据类型 + - _Requirements: 4.1, 4.4_ + + - [x] 1.3 配置 DbContext + - 在 `HoneyBoxDbContext.cs` 中添加 `DbSet` + - 配置 Fluent API 映射 + - _Requirements: 4.2, 4.3_ + +- [x] 2. 创建数据迁移脚本 + - [x] 2.1 创建 `migrate_float_ball.js` 迁移脚本 + - 参考 `migrate_coupons.js` 的模式 + - 实现 MySQL 到 SQL Server 的数据迁移 + - 支持增量迁移(跳过已迁移记录) + - 包含错误处理和日志记录 + - _Requirements: 2.1, 2.2, 2.3, 2.5, 2.6_ + + - [x] 2.2 执行数据迁移并验证 + - 运行迁移脚本 + - 验证记录数一致性 + - _Requirements: 2.4_ + +- [x] 3. 实现 API 接口 + - [x] 3.1 创建 FloatBallResponse DTO + - 在 `HoneyBox.Model/Models/FloatBall/` 目录创建响应模型 + - 排除 status, created_at, updated_at 字段 + - _Requirements: 3.3, 3.4_ + + - [x] 3.2 创建 IFloatBallService 接口和实现 + - 在 `HoneyBox.Core/Interfaces/` 创建接口 + - 在 `HoneyBox.Core/Services/` 创建实现 + - 实现 GetEnabledFloatBallsAsync 方法 + - _Requirements: 3.1, 3.2_ + + - [x] 3.3 创建 API 端点 + - 在 `ConfigController.cs` 或新建 `FloatBallController.cs` 添加端点 + - 实现 `GET /api/getFloatBall` 接口 + - 设置 `[AllowAnonymous]` 属性 + - _Requirements: 3.5, 3.6, 5.1, 5.2, 5.3_ + + - [x] 3.4 注册服务依赖 + - 在 Autofac 模块中注册 IFloatBallService + - _Requirements: 4.3_ + +- [x] 4. Checkpoint - 验证功能 + - 确保所有代码编译通过 + - 测试 API 接口返回正确数据 + - 验证响应格式与 PHP 后端一致 + - 如有问题请询问用户 + +- [ ]* 5. 编写测试 + - [ ]* 5.1 编写单元测试 + - 测试 FloatBallService 只返回启用的配置 + - 测试空结果场景 + - **Property 4: API Returns Only Enabled Configurations** + - **Validates: Requirements 3.1, 3.2** + + - [ ]* 5.2 编写集成测试 + - 测试完整 API 端点 + - 验证响应格式 + - **Property 6: API Response Format Consistency** + - **Validates: Requirements 5.1, 5.2** + +## Notes + +- 任务标记 `*` 为可选任务,可跳过以加快 MVP 开发 +- 每个任务引用具体需求以便追溯 +- 数据迁移脚本参考现有的 `migrate_coupons.js` 模式 +- API 端点无需认证,使用 `[AllowAnonymous]` 属性 diff --git a/server/C#/HoneyBox/.vs/HoneyBox/DesignTimeBuild/.dtbcache.v2 b/server/C#/HoneyBox/.vs/HoneyBox/DesignTimeBuild/.dtbcache.v2 index 804a3361..49fdc6b8 100644 Binary files a/server/C#/HoneyBox/.vs/HoneyBox/DesignTimeBuild/.dtbcache.v2 and b/server/C#/HoneyBox/.vs/HoneyBox/DesignTimeBuild/.dtbcache.v2 differ diff --git a/server/C#/HoneyBox/.vs/HoneyBox/v18/.suo b/server/C#/HoneyBox/.vs/HoneyBox/v18/.suo index 8a6b792c..ad3a6e4e 100644 Binary files a/server/C#/HoneyBox/.vs/HoneyBox/v18/.suo and b/server/C#/HoneyBox/.vs/HoneyBox/v18/.suo differ diff --git a/server/C#/HoneyBox/.vs/HoneyBox/v18/DocumentLayout.backup.json b/server/C#/HoneyBox/.vs/HoneyBox/v18/DocumentLayout.backup.json index 674e0171..596dc66d 100644 --- a/server/C#/HoneyBox/.vs/HoneyBox/v18/DocumentLayout.backup.json +++ b/server/C#/HoneyBox/.vs/HoneyBox/v18/DocumentLayout.backup.json @@ -2,14 +2,22 @@ "Version": 1, "WorkspaceRootPath": "D:\\outsource\\HaniBlindBox\\server\\C#\\HoneyBox\\", "Documents": [ - { - "AbsoluteMoniker": "D:0:0:{73C88F2C-A98A-4E84-A61C-02FBA69416A4}|src\\HoneyBox.Api\\HoneyBox.Api.csproj|d:\\outsource\\haniblindbox\\server\\c#\\honeybox\\src\\honeybox.api\\controllers\\goodscontroller.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", - "RelativeMoniker": "D:0:0:{73C88F2C-A98A-4E84-A61C-02FBA69416A4}|src\\HoneyBox.Api\\HoneyBox.Api.csproj|solutionrelative:src\\honeybox.api\\controllers\\goodscontroller.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" - }, { "AbsoluteMoniker": "D:0:0:{73C88F2C-A98A-4E84-A61C-02FBA69416A4}|src\\HoneyBox.Api\\HoneyBox.Api.csproj|d:\\outsource\\haniblindbox\\server\\c#\\honeybox\\src\\honeybox.api\\controllers\\configcontroller.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", "RelativeMoniker": "D:0:0:{73C88F2C-A98A-4E84-A61C-02FBA69416A4}|src\\HoneyBox.Api\\HoneyBox.Api.csproj|solutionrelative:src\\honeybox.api\\controllers\\configcontroller.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" }, + { + "AbsoluteMoniker": "D:0:0:{B3732485-B324-43A2-AEB0-092AD84A1302}|src\\HoneyBox.Model\\HoneyBox.Model.csproj|d:\\outsource\\haniblindbox\\server\\c#\\honeybox\\src\\honeybox.model\\data\\honeyboxdbcontext.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", + "RelativeMoniker": "D:0:0:{B3732485-B324-43A2-AEB0-092AD84A1302}|src\\HoneyBox.Model\\HoneyBox.Model.csproj|solutionrelative:src\\honeybox.model\\data\\honeyboxdbcontext.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" + }, + { + "AbsoluteMoniker": "D:0:0:{B3732485-B324-43A2-AEB0-092AD84A1302}|src\\HoneyBox.Model\\HoneyBox.Model.csproj|d:\\outsource\\haniblindbox\\server\\c#\\honeybox\\src\\honeybox.model\\entities\\useraddress.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", + "RelativeMoniker": "D:0:0:{B3732485-B324-43A2-AEB0-092AD84A1302}|src\\HoneyBox.Model\\HoneyBox.Model.csproj|solutionrelative:src\\honeybox.model\\entities\\useraddress.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" + }, + { + "AbsoluteMoniker": "D:0:0:{73C88F2C-A98A-4E84-A61C-02FBA69416A4}|src\\HoneyBox.Api\\HoneyBox.Api.csproj|d:\\outsource\\haniblindbox\\server\\c#\\honeybox\\src\\honeybox.api\\controllers\\goodscontroller.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", + "RelativeMoniker": "D:0:0:{73C88F2C-A98A-4E84-A61C-02FBA69416A4}|src\\HoneyBox.Api\\HoneyBox.Api.csproj|solutionrelative:src\\honeybox.api\\controllers\\goodscontroller.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" + }, { "AbsoluteMoniker": "D:0:0:{73C88F2C-A98A-4E84-A61C-02FBA69416A4}|src\\HoneyBox.Api\\HoneyBox.Api.csproj|d:\\outsource\\haniblindbox\\server\\c#\\honeybox\\src\\honeybox.api\\controllers\\paycontroller.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", "RelativeMoniker": "D:0:0:{73C88F2C-A98A-4E84-A61C-02FBA69416A4}|src\\HoneyBox.Api\\HoneyBox.Api.csproj|solutionrelative:src\\honeybox.api\\controllers\\paycontroller.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" @@ -34,10 +42,6 @@ "AbsoluteMoniker": "D:0:0:{73C88F2C-A98A-4E84-A61C-02FBA69416A4}|src\\HoneyBox.Api\\HoneyBox.Api.csproj|d:\\outsource\\haniblindbox\\server\\c#\\honeybox\\src\\honeybox.api\\program.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", "RelativeMoniker": "D:0:0:{73C88F2C-A98A-4E84-A61C-02FBA69416A4}|src\\HoneyBox.Api\\HoneyBox.Api.csproj|solutionrelative:src\\honeybox.api\\program.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" }, - { - "AbsoluteMoniker": "D:0:0:{B3732485-B324-43A2-AEB0-092AD84A1302}|src\\HoneyBox.Model\\HoneyBox.Model.csproj|d:\\outsource\\haniblindbox\\server\\c#\\honeybox\\src\\honeybox.model\\data\\honeyboxdbcontext.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", - "RelativeMoniker": "D:0:0:{B3732485-B324-43A2-AEB0-092AD84A1302}|src\\HoneyBox.Model\\HoneyBox.Model.csproj|solutionrelative:src\\honeybox.model\\data\\honeyboxdbcontext.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" - }, { "AbsoluteMoniker": "D:0:0:{B3732485-B324-43A2-AEB0-092AD84A1302}|src\\HoneyBox.Model\\HoneyBox.Model.csproj|d:\\outsource\\haniblindbox\\server\\c#\\honeybox\\src\\honeybox.model\\base\\apiresponse.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", "RelativeMoniker": "D:0:0:{B3732485-B324-43A2-AEB0-092AD84A1302}|src\\HoneyBox.Model\\HoneyBox.Model.csproj|solutionrelative:src\\honeybox.model\\base\\apiresponse.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" @@ -58,7 +62,7 @@ "DocumentGroups": [ { "DockedWidth": 200, - "SelectedChildIndex": 7, + "SelectedChildIndex": 4, "Children": [ { "$type": "Bookmark", @@ -67,19 +71,32 @@ { "$type": "Document", "DocumentIndex": 2, + "Title": "UserAddress.cs", + "DocumentMoniker": "D:\\outsource\\HaniBlindBox\\server\\C#\\HoneyBox\\src\\HoneyBox.Model\\Entities\\UserAddress.cs", + "RelativeDocumentMoniker": "src\\HoneyBox.Model\\Entities\\UserAddress.cs", + "ToolTip": "D:\\outsource\\HaniBlindBox\\server\\C#\\HoneyBox\\src\\HoneyBox.Model\\Entities\\UserAddress.cs", + "RelativeToolTip": "src\\HoneyBox.Model\\Entities\\UserAddress.cs", + "ViewState": "AgIAAAAAAAAAAAAAAADwvwAAAAAAAAAAAAAAAA==", + "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|", + "WhenOpened": "2026-01-03T09:17:05.325Z", + "EditorCaption": "" + }, + { + "$type": "Document", + "DocumentIndex": 4, "Title": "PayController.cs", "DocumentMoniker": "D:\\outsource\\HaniBlindBox\\server\\C#\\HoneyBox\\src\\HoneyBox.Api\\Controllers\\PayController.cs", "RelativeDocumentMoniker": "src\\HoneyBox.Api\\Controllers\\PayController.cs", "ToolTip": "D:\\outsource\\HaniBlindBox\\server\\C#\\HoneyBox\\src\\HoneyBox.Api\\Controllers\\PayController.cs", "RelativeToolTip": "src\\HoneyBox.Api\\Controllers\\PayController.cs", - "ViewState": "AgIAAB8AAAAAAAAAAAAAwAAAAAAAAAAAAAAAAA==", + "ViewState": "AgIAACIAAAAAAAAAAAAAwAAAAAAAAAAAAAAAAA==", "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|", "WhenOpened": "2026-01-03T06:35:53.369Z", "EditorCaption": "" }, { "$type": "Document", - "DocumentIndex": 3, + "DocumentIndex": 5, "Title": "CouponController.cs", "DocumentMoniker": "D:\\outsource\\HaniBlindBox\\server\\C#\\HoneyBox\\src\\HoneyBox.Api\\Controllers\\CouponController.cs", "RelativeDocumentMoniker": "src\\HoneyBox.Api\\Controllers\\CouponController.cs", @@ -92,46 +109,46 @@ }, { "$type": "Document", - "DocumentIndex": 1, + "DocumentIndex": 0, "Title": "ConfigController.cs", "DocumentMoniker": "D:\\outsource\\HaniBlindBox\\server\\C#\\HoneyBox\\src\\HoneyBox.Api\\Controllers\\ConfigController.cs", "RelativeDocumentMoniker": "src\\HoneyBox.Api\\Controllers\\ConfigController.cs", "ToolTip": "D:\\outsource\\HaniBlindBox\\server\\C#\\HoneyBox\\src\\HoneyBox.Api\\Controllers\\ConfigController.cs", "RelativeToolTip": "src\\HoneyBox.Api\\Controllers\\ConfigController.cs", - "ViewState": "AgIAAEcAAAAAAAAAAAAAAFIAAAAcAAAAAAAAAA==", + "ViewState": "AgIAAIcAAAAAAAAAAADwv6QAAABOAAAAAAAAAA==", "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|", "WhenOpened": "2026-01-03T06:15:47.201Z", "EditorCaption": "" }, { "$type": "Document", - "DocumentIndex": 5, + "DocumentIndex": 7, "Title": "AuthController.cs", "DocumentMoniker": "D:\\outsource\\HaniBlindBox\\server\\C#\\HoneyBox\\src\\HoneyBox.Api\\Controllers\\AuthController.cs", "RelativeDocumentMoniker": "src\\HoneyBox.Api\\Controllers\\AuthController.cs", "ToolTip": "D:\\outsource\\HaniBlindBox\\server\\C#\\HoneyBox\\src\\HoneyBox.Api\\Controllers\\AuthController.cs", "RelativeToolTip": "src\\HoneyBox.Api\\Controllers\\AuthController.cs", - "ViewState": "AgIAAGYAAAAAAAAAAAAuwPcAAAAWAAAAAAAAAA==", + "ViewState": "AgIAAGYAAAAAAAAAAAAuwCUBAAAWAAAAAAAAAA==", "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|", "WhenOpened": "2026-01-03T06:07:38.674Z", "EditorCaption": "" }, { "$type": "Document", - "DocumentIndex": 4, + "DocumentIndex": 6, "Title": "CollectionController.cs", "DocumentMoniker": "D:\\outsource\\HaniBlindBox\\server\\C#\\HoneyBox\\src\\HoneyBox.Api\\Controllers\\CollectionController.cs", "RelativeDocumentMoniker": "src\\HoneyBox.Api\\Controllers\\CollectionController.cs", "ToolTip": "D:\\outsource\\HaniBlindBox\\server\\C#\\HoneyBox\\src\\HoneyBox.Api\\Controllers\\CollectionController.cs", "RelativeToolTip": "src\\HoneyBox.Api\\Controllers\\CollectionController.cs", - "ViewState": "AgIAAL0AAAAAAAAAAAAAAM0AAAAUAAAAAAAAAA==", + "ViewState": "AgIAAA0BAAAAAAAAAAAAAEkBAAAUAAAAAAAAAA==", "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|", "WhenOpened": "2026-01-03T06:07:37.824Z", "EditorCaption": "" }, { "$type": "Document", - "DocumentIndex": 6, + "DocumentIndex": 8, "Title": "appsettings.json", "DocumentMoniker": "D:\\outsource\\HaniBlindBox\\server\\C#\\HoneyBox\\src\\HoneyBox.Api\\appsettings.json", "RelativeDocumentMoniker": "src\\HoneyBox.Api\\appsettings.json", @@ -144,33 +161,33 @@ }, { "$type": "Document", - "DocumentIndex": 0, + "DocumentIndex": 3, "Title": "GoodsController.cs", "DocumentMoniker": "D:\\outsource\\HaniBlindBox\\server\\C#\\HoneyBox\\src\\HoneyBox.Api\\Controllers\\GoodsController.cs", "RelativeDocumentMoniker": "src\\HoneyBox.Api\\Controllers\\GoodsController.cs", "ToolTip": "D:\\outsource\\HaniBlindBox\\server\\C#\\HoneyBox\\src\\HoneyBox.Api\\Controllers\\GoodsController.cs", "RelativeToolTip": "src\\HoneyBox.Api\\Controllers\\GoodsController.cs", - "ViewState": "AgIAACgAAAAAAAAAAAAuwFMAAABUAAAAAAAAAA==", + "ViewState": "AgIAAD0AAAAAAAAAAAAAAFkAAAAIAAAAAAAAAA==", "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|", "WhenOpened": "2026-01-03T05:07:08.788Z", "EditorCaption": "" }, { "$type": "Document", - "DocumentIndex": 8, + "DocumentIndex": 1, "Title": "HoneyBoxDbContext.cs", "DocumentMoniker": "D:\\outsource\\HaniBlindBox\\server\\C#\\HoneyBox\\src\\HoneyBox.Model\\Data\\HoneyBoxDbContext.cs", "RelativeDocumentMoniker": "src\\HoneyBox.Model\\Data\\HoneyBoxDbContext.cs", "ToolTip": "D:\\outsource\\HaniBlindBox\\server\\C#\\HoneyBox\\src\\HoneyBox.Model\\Data\\HoneyBoxDbContext.cs", "RelativeToolTip": "src\\HoneyBox.Model\\Data\\HoneyBoxDbContext.cs", - "ViewState": "AgIAADkAAAAAAAAAAAAcwEYAAAA+AAAAAAAAAA==", + "ViewState": "AgIAAFUAAAAAAAAAAAAUwHAAAAAqAAAAAAAAAA==", "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|", "WhenOpened": "2026-01-02T06:46:26.809Z", "EditorCaption": "" }, { "$type": "Document", - "DocumentIndex": 7, + "DocumentIndex": 9, "Title": "Program.cs", "DocumentMoniker": "D:\\outsource\\HaniBlindBox\\server\\C#\\HoneyBox\\src\\HoneyBox.Api\\Program.cs", "RelativeDocumentMoniker": "src\\HoneyBox.Api\\Program.cs", @@ -183,7 +200,7 @@ }, { "$type": "Document", - "DocumentIndex": 9, + "DocumentIndex": 10, "Title": "ApiResponse.cs", "DocumentMoniker": "D:\\outsource\\HaniBlindBox\\server\\C#\\HoneyBox\\src\\HoneyBox.Model\\Base\\ApiResponse.cs", "RelativeDocumentMoniker": "src\\HoneyBox.Model\\Base\\ApiResponse.cs", @@ -195,7 +212,7 @@ }, { "$type": "Document", - "DocumentIndex": 10, + "DocumentIndex": 11, "Title": "PrizeModels.cs", "DocumentMoniker": "D:\\outsource\\HaniBlindBox\\server\\C#\\HoneyBox\\src\\HoneyBox.Model\\Models\\Prize\\PrizeModels.cs", "RelativeDocumentMoniker": "src\\HoneyBox.Model\\Models\\Prize\\PrizeModels.cs", @@ -207,7 +224,7 @@ }, { "$type": "Document", - "DocumentIndex": 11, + "DocumentIndex": 12, "Title": "T_Task.cs", "DocumentMoniker": "D:\\outsource\\HaniBlindBox\\server\\C#\\HoneyBox\\src\\HoneyBox.Model\\Entities\\T_Task.cs", "RelativeDocumentMoniker": "src\\HoneyBox.Model\\Entities\\T_Task.cs", diff --git a/server/C#/HoneyBox/.vs/HoneyBox/v18/DocumentLayout.json b/server/C#/HoneyBox/.vs/HoneyBox/v18/DocumentLayout.json index 33696e24..4d28996c 100644 --- a/server/C#/HoneyBox/.vs/HoneyBox/v18/DocumentLayout.json +++ b/server/C#/HoneyBox/.vs/HoneyBox/v18/DocumentLayout.json @@ -2,18 +2,6 @@ "Version": 1, "WorkspaceRootPath": "D:\\outsource\\HaniBlindBox\\server\\C#\\HoneyBox\\", "Documents": [ - { - "AbsoluteMoniker": "D:0:0:{73C88F2C-A98A-4E84-A61C-02FBA69416A4}|src\\HoneyBox.Api\\HoneyBox.Api.csproj|d:\\outsource\\haniblindbox\\server\\c#\\honeybox\\src\\honeybox.api\\controllers\\goodscontroller.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", - "RelativeMoniker": "D:0:0:{73C88F2C-A98A-4E84-A61C-02FBA69416A4}|src\\HoneyBox.Api\\HoneyBox.Api.csproj|solutionrelative:src\\honeybox.api\\controllers\\goodscontroller.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" - }, - { - "AbsoluteMoniker": "D:0:0:{73C88F2C-A98A-4E84-A61C-02FBA69416A4}|src\\HoneyBox.Api\\HoneyBox.Api.csproj|d:\\outsource\\haniblindbox\\server\\c#\\honeybox\\src\\honeybox.api\\controllers\\configcontroller.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", - "RelativeMoniker": "D:0:0:{73C88F2C-A98A-4E84-A61C-02FBA69416A4}|src\\HoneyBox.Api\\HoneyBox.Api.csproj|solutionrelative:src\\honeybox.api\\controllers\\configcontroller.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" - }, - { - "AbsoluteMoniker": "D:0:0:{73C88F2C-A98A-4E84-A61C-02FBA69416A4}|src\\HoneyBox.Api\\HoneyBox.Api.csproj|d:\\outsource\\haniblindbox\\server\\c#\\honeybox\\src\\honeybox.api\\controllers\\paycontroller.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", - "RelativeMoniker": "D:0:0:{73C88F2C-A98A-4E84-A61C-02FBA69416A4}|src\\HoneyBox.Api\\HoneyBox.Api.csproj|solutionrelative:src\\honeybox.api\\controllers\\paycontroller.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" - }, { "AbsoluteMoniker": "D:0:0:{73C88F2C-A98A-4E84-A61C-02FBA69416A4}|src\\HoneyBox.Api\\HoneyBox.Api.csproj|d:\\outsource\\haniblindbox\\server\\c#\\honeybox\\src\\honeybox.api\\controllers\\couponcontroller.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", "RelativeMoniker": "D:0:0:{73C88F2C-A98A-4E84-A61C-02FBA69416A4}|src\\HoneyBox.Api\\HoneyBox.Api.csproj|solutionrelative:src\\honeybox.api\\controllers\\couponcontroller.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" @@ -26,6 +14,34 @@ "AbsoluteMoniker": "D:0:0:{73C88F2C-A98A-4E84-A61C-02FBA69416A4}|src\\HoneyBox.Api\\HoneyBox.Api.csproj|d:\\outsource\\haniblindbox\\server\\c#\\honeybox\\src\\honeybox.api\\controllers\\authcontroller.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", "RelativeMoniker": "D:0:0:{73C88F2C-A98A-4E84-A61C-02FBA69416A4}|src\\HoneyBox.Api\\HoneyBox.Api.csproj|solutionrelative:src\\honeybox.api\\controllers\\authcontroller.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" }, + { + "AbsoluteMoniker": "D:0:0:{73C88F2C-A98A-4E84-A61C-02FBA69416A4}|src\\HoneyBox.Api\\HoneyBox.Api.csproj|d:\\outsource\\haniblindbox\\server\\c#\\honeybox\\src\\honeybox.api\\controllers\\addresscontroller.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", + "RelativeMoniker": "D:0:0:{73C88F2C-A98A-4E84-A61C-02FBA69416A4}|src\\HoneyBox.Api\\HoneyBox.Api.csproj|solutionrelative:src\\honeybox.api\\controllers\\addresscontroller.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" + }, + { + "AbsoluteMoniker": "D:0:0:{73C88F2C-A98A-4E84-A61C-02FBA69416A4}|src\\HoneyBox.Api\\HoneyBox.Api.csproj|d:\\outsource\\haniblindbox\\server\\c#\\honeybox\\src\\honeybox.api\\controllers\\redeemcontroller.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", + "RelativeMoniker": "D:0:0:{73C88F2C-A98A-4E84-A61C-02FBA69416A4}|src\\HoneyBox.Api\\HoneyBox.Api.csproj|solutionrelative:src\\honeybox.api\\controllers\\redeemcontroller.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" + }, + { + "AbsoluteMoniker": "D:0:0:{73C88F2C-A98A-4E84-A61C-02FBA69416A4}|src\\HoneyBox.Api\\HoneyBox.Api.csproj|d:\\outsource\\haniblindbox\\server\\c#\\honeybox\\src\\honeybox.api\\controllers\\configcontroller.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", + "RelativeMoniker": "D:0:0:{73C88F2C-A98A-4E84-A61C-02FBA69416A4}|src\\HoneyBox.Api\\HoneyBox.Api.csproj|solutionrelative:src\\honeybox.api\\controllers\\configcontroller.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" + }, + { + "AbsoluteMoniker": "D:0:0:{B3732485-B324-43A2-AEB0-092AD84A1302}|src\\HoneyBox.Model\\HoneyBox.Model.csproj|d:\\outsource\\haniblindbox\\server\\c#\\honeybox\\src\\honeybox.model\\data\\honeyboxdbcontext.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", + "RelativeMoniker": "D:0:0:{B3732485-B324-43A2-AEB0-092AD84A1302}|src\\HoneyBox.Model\\HoneyBox.Model.csproj|solutionrelative:src\\honeybox.model\\data\\honeyboxdbcontext.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" + }, + { + "AbsoluteMoniker": "D:0:0:{B3732485-B324-43A2-AEB0-092AD84A1302}|src\\HoneyBox.Model\\HoneyBox.Model.csproj|d:\\outsource\\haniblindbox\\server\\c#\\honeybox\\src\\honeybox.model\\entities\\useraddress.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", + "RelativeMoniker": "D:0:0:{B3732485-B324-43A2-AEB0-092AD84A1302}|src\\HoneyBox.Model\\HoneyBox.Model.csproj|solutionrelative:src\\honeybox.model\\entities\\useraddress.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" + }, + { + "AbsoluteMoniker": "D:0:0:{73C88F2C-A98A-4E84-A61C-02FBA69416A4}|src\\HoneyBox.Api\\HoneyBox.Api.csproj|d:\\outsource\\haniblindbox\\server\\c#\\honeybox\\src\\honeybox.api\\controllers\\goodscontroller.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", + "RelativeMoniker": "D:0:0:{73C88F2C-A98A-4E84-A61C-02FBA69416A4}|src\\HoneyBox.Api\\HoneyBox.Api.csproj|solutionrelative:src\\honeybox.api\\controllers\\goodscontroller.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" + }, + { + "AbsoluteMoniker": "D:0:0:{73C88F2C-A98A-4E84-A61C-02FBA69416A4}|src\\HoneyBox.Api\\HoneyBox.Api.csproj|d:\\outsource\\haniblindbox\\server\\c#\\honeybox\\src\\honeybox.api\\controllers\\paycontroller.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", + "RelativeMoniker": "D:0:0:{73C88F2C-A98A-4E84-A61C-02FBA69416A4}|src\\HoneyBox.Api\\HoneyBox.Api.csproj|solutionrelative:src\\honeybox.api\\controllers\\paycontroller.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" + }, { "AbsoluteMoniker": "D:0:0:{73C88F2C-A98A-4E84-A61C-02FBA69416A4}|src\\HoneyBox.Api\\HoneyBox.Api.csproj|d:\\outsource\\haniblindbox\\server\\c#\\honeybox\\src\\honeybox.api\\appsettings.json||{90A6B3A7-C1A3-4009-A288-E2FF89E96FA0}", "RelativeMoniker": "D:0:0:{73C88F2C-A98A-4E84-A61C-02FBA69416A4}|src\\HoneyBox.Api\\HoneyBox.Api.csproj|solutionrelative:src\\honeybox.api\\appsettings.json||{90A6B3A7-C1A3-4009-A288-E2FF89E96FA0}" @@ -34,10 +50,6 @@ "AbsoluteMoniker": "D:0:0:{73C88F2C-A98A-4E84-A61C-02FBA69416A4}|src\\HoneyBox.Api\\HoneyBox.Api.csproj|d:\\outsource\\haniblindbox\\server\\c#\\honeybox\\src\\honeybox.api\\program.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", "RelativeMoniker": "D:0:0:{73C88F2C-A98A-4E84-A61C-02FBA69416A4}|src\\HoneyBox.Api\\HoneyBox.Api.csproj|solutionrelative:src\\honeybox.api\\program.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" }, - { - "AbsoluteMoniker": "D:0:0:{B3732485-B324-43A2-AEB0-092AD84A1302}|src\\HoneyBox.Model\\HoneyBox.Model.csproj|d:\\outsource\\haniblindbox\\server\\c#\\honeybox\\src\\honeybox.model\\data\\honeyboxdbcontext.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", - "RelativeMoniker": "D:0:0:{B3732485-B324-43A2-AEB0-092AD84A1302}|src\\HoneyBox.Model\\HoneyBox.Model.csproj|solutionrelative:src\\honeybox.model\\data\\honeyboxdbcontext.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" - }, { "AbsoluteMoniker": "D:0:0:{B3732485-B324-43A2-AEB0-092AD84A1302}|src\\HoneyBox.Model\\HoneyBox.Model.csproj|d:\\outsource\\haniblindbox\\server\\c#\\honeybox\\src\\honeybox.model\\base\\apiresponse.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", "RelativeMoniker": "D:0:0:{B3732485-B324-43A2-AEB0-092AD84A1302}|src\\HoneyBox.Model\\HoneyBox.Model.csproj|solutionrelative:src\\honeybox.model\\base\\apiresponse.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" @@ -58,7 +70,7 @@ "DocumentGroups": [ { "DockedWidth": 200, - "SelectedChildIndex": 7, + "SelectedChildIndex": 5, "Children": [ { "$type": "Bookmark", @@ -66,72 +78,111 @@ }, { "$type": "Document", - "DocumentIndex": 2, + "DocumentIndex": 4, + "Title": "RedeemController.cs", + "DocumentMoniker": "D:\\outsource\\HaniBlindBox\\server\\C#\\HoneyBox\\src\\HoneyBox.Api\\Controllers\\RedeemController.cs", + "RelativeDocumentMoniker": "src\\HoneyBox.Api\\Controllers\\RedeemController.cs", + "ToolTip": "D:\\outsource\\HaniBlindBox\\server\\C#\\HoneyBox\\src\\HoneyBox.Api\\Controllers\\RedeemController.cs", + "RelativeToolTip": "src\\HoneyBox.Api\\Controllers\\RedeemController.cs", + "ViewState": "AgIAAFIAAAAAAAAAAADwvwAAAAAAAAAAAAAAAA==", + "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|", + "WhenOpened": "2026-01-03T11:40:46.393Z", + "EditorCaption": "" + }, + { + "$type": "Document", + "DocumentIndex": 3, + "Title": "AddressController.cs", + "DocumentMoniker": "D:\\outsource\\HaniBlindBox\\server\\C#\\HoneyBox\\src\\HoneyBox.Api\\Controllers\\AddressController.cs", + "RelativeDocumentMoniker": "src\\HoneyBox.Api\\Controllers\\AddressController.cs", + "ToolTip": "D:\\outsource\\HaniBlindBox\\server\\C#\\HoneyBox\\src\\HoneyBox.Api\\Controllers\\AddressController.cs", + "RelativeToolTip": "src\\HoneyBox.Api\\Controllers\\AddressController.cs", + "ViewState": "AgIAAGkAAAAAAAAAAADwvwAAAAAAAAAAAAAAAA==", + "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|", + "WhenOpened": "2026-01-03T11:40:39.283Z", + "EditorCaption": "" + }, + { + "$type": "Document", + "DocumentIndex": 7, + "Title": "UserAddress.cs", + "DocumentMoniker": "D:\\outsource\\HaniBlindBox\\server\\C#\\HoneyBox\\src\\HoneyBox.Model\\Entities\\UserAddress.cs", + "RelativeDocumentMoniker": "src\\HoneyBox.Model\\Entities\\UserAddress.cs", + "ToolTip": "D:\\outsource\\HaniBlindBox\\server\\C#\\HoneyBox\\src\\HoneyBox.Model\\Entities\\UserAddress.cs", + "RelativeToolTip": "src\\HoneyBox.Model\\Entities\\UserAddress.cs", + "ViewState": "AgIAAAAAAAAAAAAAAADwvwAAAAAAAAAAAAAAAA==", + "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|", + "WhenOpened": "2026-01-03T09:17:05.325Z", + "EditorCaption": "" + }, + { + "$type": "Document", + "DocumentIndex": 9, "Title": "PayController.cs", "DocumentMoniker": "D:\\outsource\\HaniBlindBox\\server\\C#\\HoneyBox\\src\\HoneyBox.Api\\Controllers\\PayController.cs", "RelativeDocumentMoniker": "src\\HoneyBox.Api\\Controllers\\PayController.cs", "ToolTip": "D:\\outsource\\HaniBlindBox\\server\\C#\\HoneyBox\\src\\HoneyBox.Api\\Controllers\\PayController.cs", "RelativeToolTip": "src\\HoneyBox.Api\\Controllers\\PayController.cs", - "ViewState": "AgIAAB8AAAAAAAAAAAAAwAAAAAAAAAAAAAAAAA==", + "ViewState": "AgIAACIAAAAAAAAAAAAAwAAAAAAAAAAAAAAAAA==", "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|", "WhenOpened": "2026-01-03T06:35:53.369Z", "EditorCaption": "" }, { "$type": "Document", - "DocumentIndex": 3, + "DocumentIndex": 0, "Title": "CouponController.cs", "DocumentMoniker": "D:\\outsource\\HaniBlindBox\\server\\C#\\HoneyBox\\src\\HoneyBox.Api\\Controllers\\CouponController.cs", "RelativeDocumentMoniker": "src\\HoneyBox.Api\\Controllers\\CouponController.cs", "ToolTip": "D:\\outsource\\HaniBlindBox\\server\\C#\\HoneyBox\\src\\HoneyBox.Api\\Controllers\\CouponController.cs", "RelativeToolTip": "src\\HoneyBox.Api\\Controllers\\CouponController.cs", - "ViewState": "AgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==", + "ViewState": "AgIAADEBAAAAAAAAAAAAAEQAAAA3AAAAAAAAAA==", "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|", "WhenOpened": "2026-01-03T06:35:48.625Z", "EditorCaption": "" }, { "$type": "Document", - "DocumentIndex": 1, + "DocumentIndex": 5, "Title": "ConfigController.cs", "DocumentMoniker": "D:\\outsource\\HaniBlindBox\\server\\C#\\HoneyBox\\src\\HoneyBox.Api\\Controllers\\ConfigController.cs", "RelativeDocumentMoniker": "src\\HoneyBox.Api\\Controllers\\ConfigController.cs", "ToolTip": "D:\\outsource\\HaniBlindBox\\server\\C#\\HoneyBox\\src\\HoneyBox.Api\\Controllers\\ConfigController.cs", "RelativeToolTip": "src\\HoneyBox.Api\\Controllers\\ConfigController.cs", - "ViewState": "AgIAAEcAAAAAAAAAAAAAAFIAAAAcAAAAAAAAAA==", + "ViewState": "AgIAAJsAAAAAAAAAAADwv7QAAAANAAAAAAAAAA==", "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|", "WhenOpened": "2026-01-03T06:15:47.201Z", "EditorCaption": "" }, { "$type": "Document", - "DocumentIndex": 5, + "DocumentIndex": 2, "Title": "AuthController.cs", "DocumentMoniker": "D:\\outsource\\HaniBlindBox\\server\\C#\\HoneyBox\\src\\HoneyBox.Api\\Controllers\\AuthController.cs", "RelativeDocumentMoniker": "src\\HoneyBox.Api\\Controllers\\AuthController.cs", "ToolTip": "D:\\outsource\\HaniBlindBox\\server\\C#\\HoneyBox\\src\\HoneyBox.Api\\Controllers\\AuthController.cs", "RelativeToolTip": "src\\HoneyBox.Api\\Controllers\\AuthController.cs", - "ViewState": "AgIAAGYAAAAAAAAAAAAuwPcAAAAWAAAAAAAAAA==", + "ViewState": "AgIAAC4BAAAAAAAAAAAuwCUBAAAWAAAAAAAAAA==", "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|", "WhenOpened": "2026-01-03T06:07:38.674Z", "EditorCaption": "" }, { "$type": "Document", - "DocumentIndex": 4, + "DocumentIndex": 1, "Title": "CollectionController.cs", "DocumentMoniker": "D:\\outsource\\HaniBlindBox\\server\\C#\\HoneyBox\\src\\HoneyBox.Api\\Controllers\\CollectionController.cs", "RelativeDocumentMoniker": "src\\HoneyBox.Api\\Controllers\\CollectionController.cs", "ToolTip": "D:\\outsource\\HaniBlindBox\\server\\C#\\HoneyBox\\src\\HoneyBox.Api\\Controllers\\CollectionController.cs", "RelativeToolTip": "src\\HoneyBox.Api\\Controllers\\CollectionController.cs", - "ViewState": "AgIAAL0AAAAAAAAAAAAAAM0AAAAUAAAAAAAAAA==", + "ViewState": "AgIAAFMBAAAAAAAAAADwvyABAAAZAAAAAAAAAA==", "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|", "WhenOpened": "2026-01-03T06:07:37.824Z", "EditorCaption": "" }, { "$type": "Document", - "DocumentIndex": 6, + "DocumentIndex": 10, "Title": "appsettings.json", "DocumentMoniker": "D:\\outsource\\HaniBlindBox\\server\\C#\\HoneyBox\\src\\HoneyBox.Api\\appsettings.json", "RelativeDocumentMoniker": "src\\HoneyBox.Api\\appsettings.json", @@ -144,33 +195,33 @@ }, { "$type": "Document", - "DocumentIndex": 0, + "DocumentIndex": 8, "Title": "GoodsController.cs", "DocumentMoniker": "D:\\outsource\\HaniBlindBox\\server\\C#\\HoneyBox\\src\\HoneyBox.Api\\Controllers\\GoodsController.cs", "RelativeDocumentMoniker": "src\\HoneyBox.Api\\Controllers\\GoodsController.cs", "ToolTip": "D:\\outsource\\HaniBlindBox\\server\\C#\\HoneyBox\\src\\HoneyBox.Api\\Controllers\\GoodsController.cs", "RelativeToolTip": "src\\HoneyBox.Api\\Controllers\\GoodsController.cs", - "ViewState": "AgIAADQAAAAAAAAAAAAAAF0AAAAMAAAAAAAAAA==", + "ViewState": "AgIAAD0AAAAAAAAAAAAAAFkAAAAIAAAAAAAAAA==", "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|", "WhenOpened": "2026-01-03T05:07:08.788Z", "EditorCaption": "" }, { "$type": "Document", - "DocumentIndex": 8, + "DocumentIndex": 6, "Title": "HoneyBoxDbContext.cs", "DocumentMoniker": "D:\\outsource\\HaniBlindBox\\server\\C#\\HoneyBox\\src\\HoneyBox.Model\\Data\\HoneyBoxDbContext.cs", "RelativeDocumentMoniker": "src\\HoneyBox.Model\\Data\\HoneyBoxDbContext.cs", "ToolTip": "D:\\outsource\\HaniBlindBox\\server\\C#\\HoneyBox\\src\\HoneyBox.Model\\Data\\HoneyBoxDbContext.cs", "RelativeToolTip": "src\\HoneyBox.Model\\Data\\HoneyBoxDbContext.cs", - "ViewState": "AgIAADkAAAAAAAAAAAAcwEYAAAA+AAAAAAAAAA==", + "ViewState": "AgIAAFUAAAAAAAAAAAAUwHIAAAAqAAAAAAAAAA==", "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|", "WhenOpened": "2026-01-02T06:46:26.809Z", "EditorCaption": "" }, { "$type": "Document", - "DocumentIndex": 7, + "DocumentIndex": 11, "Title": "Program.cs", "DocumentMoniker": "D:\\outsource\\HaniBlindBox\\server\\C#\\HoneyBox\\src\\HoneyBox.Api\\Program.cs", "RelativeDocumentMoniker": "src\\HoneyBox.Api\\Program.cs", @@ -183,7 +234,7 @@ }, { "$type": "Document", - "DocumentIndex": 9, + "DocumentIndex": 12, "Title": "ApiResponse.cs", "DocumentMoniker": "D:\\outsource\\HaniBlindBox\\server\\C#\\HoneyBox\\src\\HoneyBox.Model\\Base\\ApiResponse.cs", "RelativeDocumentMoniker": "src\\HoneyBox.Model\\Base\\ApiResponse.cs", @@ -195,7 +246,7 @@ }, { "$type": "Document", - "DocumentIndex": 10, + "DocumentIndex": 13, "Title": "PrizeModels.cs", "DocumentMoniker": "D:\\outsource\\HaniBlindBox\\server\\C#\\HoneyBox\\src\\HoneyBox.Model\\Models\\Prize\\PrizeModels.cs", "RelativeDocumentMoniker": "src\\HoneyBox.Model\\Models\\Prize\\PrizeModels.cs", @@ -207,7 +258,7 @@ }, { "$type": "Document", - "DocumentIndex": 11, + "DocumentIndex": 14, "Title": "T_Task.cs", "DocumentMoniker": "D:\\outsource\\HaniBlindBox\\server\\C#\\HoneyBox\\src\\HoneyBox.Model\\Entities\\T_Task.cs", "RelativeDocumentMoniker": "src\\HoneyBox.Model\\Entities\\T_Task.cs", diff --git a/server/C#/HoneyBox/src/HoneyBox.Api/Controllers/ConfigController.cs b/server/C#/HoneyBox/src/HoneyBox.Api/Controllers/ConfigController.cs index 590675c1..282a9318 100644 --- a/server/C#/HoneyBox/src/HoneyBox.Api/Controllers/ConfigController.cs +++ b/server/C#/HoneyBox/src/HoneyBox.Api/Controllers/ConfigController.cs @@ -1,6 +1,8 @@ using HoneyBox.Core.Interfaces; using HoneyBox.Model.Base; using HoneyBox.Model.Models.Config; +using HoneyBox.Model.Models.FloatBall; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; namespace HoneyBox.Api.Controllers; @@ -9,20 +11,23 @@ namespace HoneyBox.Api.Controllers; /// 配置控制器 - 处理系统配置和平台配置 /// /// -/// 提供系统配置、商品类型、平台配置等功能 +/// 提供系统配置、商品类型、平台配置、悬浮球配置等功能 /// [ApiController] [Route("api")] public class ConfigController : ControllerBase { private readonly IConfigService _configService; + private readonly IFloatBallService _floatBallService; private readonly ILogger _logger; public ConfigController( IConfigService configService, + IFloatBallService floatBallService, ILogger logger) { _configService = configService; + _floatBallService = floatBallService; _logger = logger; } @@ -160,9 +165,9 @@ public class ConfigController : ControllerBase /// /// 请求参数 /// 单页内容数据 - [HttpPost("danye")] + [HttpGet("danye")] [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - public async Task> GetDanyeContent([FromBody] DanyeRequest? request) + public async Task> GetDanyeContent([FromQuery] DanyeRequest? request) { try { @@ -184,4 +189,32 @@ public class ConfigController : ControllerBase return ApiResponse.Fail("获取单页内容失败"); } } + + /// + /// 获取悬浮球配置 + /// + /// + /// GET /api/getFloatBall + /// + /// 返回所有启用的悬浮球配置列表 + /// 支持未登录访问 + /// Requirements: 3.1, 3.2, 3.3, 3.4, 3.5, 3.6, 5.1, 5.2, 5.3 + /// + /// 悬浮球配置列表 + [HttpGet("getFloatBall")] + [AllowAnonymous] + [ProducesResponseType(typeof(ApiResponse>), StatusCodes.Status200OK)] + public async Task>> GetFloatBall() + { + try + { + var result = await _floatBallService.GetEnabledFloatBallsAsync(); + return ApiResponse>.Success(result, "获取悬浮球配置成功"); + } + catch (Exception ex) + { + _logger.LogError(ex, "Failed to get float ball configurations"); + return ApiResponse>.Fail("获取悬浮球配置失败"); + } + } } diff --git a/server/C#/HoneyBox/src/HoneyBox.Api/Controllers/WarehouseController.cs b/server/C#/HoneyBox/src/HoneyBox.Api/Controllers/WarehouseController.cs index 94ff5924..f9cc2897 100644 --- a/server/C#/HoneyBox/src/HoneyBox.Api/Controllers/WarehouseController.cs +++ b/server/C#/HoneyBox/src/HoneyBox.Api/Controllers/WarehouseController.cs @@ -1,4 +1,4 @@ -using System.Security.Claims; +using System.Security.Claims; using HoneyBox.Core.Interfaces; using HoneyBox.Model.Base; using HoneyBox.Model.Models; diff --git a/server/C#/HoneyBox/src/HoneyBox.Core/Interfaces/IFloatBallService.cs b/server/C#/HoneyBox/src/HoneyBox.Core/Interfaces/IFloatBallService.cs new file mode 100644 index 00000000..143de586 --- /dev/null +++ b/server/C#/HoneyBox/src/HoneyBox.Core/Interfaces/IFloatBallService.cs @@ -0,0 +1,15 @@ +using HoneyBox.Model.Models.FloatBall; + +namespace HoneyBox.Core.Interfaces; + +/// +/// 悬浮球服务接口 +/// +public interface IFloatBallService +{ + /// + /// 获取所有启用的悬浮球配置 + /// + /// 启用的悬浮球配置列表 + Task> GetEnabledFloatBallsAsync(); +} diff --git a/server/C#/HoneyBox/src/HoneyBox.Core/Services/FloatBallService.cs b/server/C#/HoneyBox/src/HoneyBox.Core/Services/FloatBallService.cs new file mode 100644 index 00000000..cd4c285a --- /dev/null +++ b/server/C#/HoneyBox/src/HoneyBox.Core/Services/FloatBallService.cs @@ -0,0 +1,61 @@ +using HoneyBox.Core.Interfaces; +using HoneyBox.Model.Data; +using HoneyBox.Model.Models.FloatBall; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; + +namespace HoneyBox.Core.Services; + +/// +/// 悬浮球服务实现 +/// +public class FloatBallService : IFloatBallService +{ + private readonly HoneyBoxDbContext _dbContext; + private readonly ILogger _logger; + + public FloatBallService( + HoneyBoxDbContext dbContext, + ILogger logger) + { + _dbContext = dbContext; + _logger = logger; + } + + /// + public async Task> GetEnabledFloatBallsAsync() + { + try + { + var floatBalls = await _dbContext.FloatBallConfigs + .Where(f => f.Status == 1) + .Select(f => new FloatBallResponse + { + Id = f.Id, + Type = f.Type, + Image = f.Image, + LinkUrl = f.LinkUrl, + PositionX = f.PositionX, + PositionY = f.PositionY, + Width = f.Width, + Height = f.Height, + Effect = f.Effect, + Title = f.Title, + ImageDetails = f.ImageDetails, + ImageBj = f.ImageBj, + ImageDetailsX = f.ImageDetailsX, + ImageDetailsY = f.ImageDetailsY, + ImageDetailsW = f.ImageDetailsW, + ImageDetailsH = f.ImageDetailsH + }) + .ToListAsync(); + + return floatBalls; + } + catch (Exception ex) + { + _logger.LogError(ex, "Failed to get enabled float ball configurations"); + throw; + } + } +} diff --git a/server/C#/HoneyBox/src/HoneyBox.Infrastructure/Modules/ServiceModule.cs b/server/C#/HoneyBox/src/HoneyBox.Infrastructure/Modules/ServiceModule.cs index 4ac9146b..42b356e2 100644 --- a/server/C#/HoneyBox/src/HoneyBox.Infrastructure/Modules/ServiceModule.cs +++ b/server/C#/HoneyBox/src/HoneyBox.Infrastructure/Modules/ServiceModule.cs @@ -277,6 +277,14 @@ public class ServiceModule : Module return new ConfigService(dbContext, logger, redisService); }).As().InstancePerLifetimeScope(); + // 注册悬浮球服务 + builder.Register(c => + { + var dbContext = c.Resolve(); + var logger = c.Resolve>(); + return new FloatBallService(dbContext, logger); + }).As().InstancePerLifetimeScope(); + // ========== 签到系统服务注册 ========== // 注册签到服务 diff --git a/server/C#/HoneyBox/src/HoneyBox.Model/Data/HoneyBoxDbContext.cs b/server/C#/HoneyBox/src/HoneyBox.Model/Data/HoneyBoxDbContext.cs index 6f9c9d2c..fcffca5a 100644 --- a/server/C#/HoneyBox/src/HoneyBox.Model/Data/HoneyBoxDbContext.cs +++ b/server/C#/HoneyBox/src/HoneyBox.Model/Data/HoneyBoxDbContext.cs @@ -108,6 +108,8 @@ public partial class HoneyBoxDbContext : DbContext public virtual DbSet Rewards { get; set; } + public virtual DbSet FloatBallConfigs { get; set; } + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { // Connection string is configured in Program.cs via dependency injection @@ -2974,6 +2976,97 @@ public partial class HoneyBoxDbContext : DbContext .HasColumnName("updated_at"); }); + modelBuilder.Entity(entity => + { + entity.HasKey(e => e.Id).HasName("pk_float_ball_configs"); + + entity.ToTable("float_ball_configs", tb => tb.HasComment("悬浮球配置表,存储首页悬浮球配置信息")); + + entity.HasIndex(e => e.Status, "ix_float_ball_configs_status"); + + entity.Property(e => e.Id) + .HasComment("主键ID") + .HasColumnName("id"); + entity.Property(e => e.Status) + .HasDefaultValue((byte)0) + .HasComment("状态: 0关闭 1开启") + .HasColumnName("status"); + entity.Property(e => e.Type) + .HasDefaultValue((byte)1) + .HasComment("类型: 1展示图片 2跳转页面") + .HasColumnName("type"); + entity.Property(e => e.Image) + .HasMaxLength(255) + .HasDefaultValue("") + .HasComment("悬浮球图片URL") + .HasColumnName("image"); + entity.Property(e => e.LinkUrl) + .HasMaxLength(255) + .HasDefaultValue("") + .HasComment("跳转链接") + .HasColumnName("link_url"); + entity.Property(e => e.PositionX) + .HasMaxLength(30) + .HasDefaultValue("") + .HasComment("X轴位置") + .HasColumnName("position_x"); + entity.Property(e => e.PositionY) + .HasMaxLength(30) + .HasDefaultValue("") + .HasComment("Y轴位置") + .HasColumnName("position_y"); + entity.Property(e => e.Width) + .HasMaxLength(30) + .HasDefaultValue("") + .HasComment("宽度") + .HasColumnName("width"); + entity.Property(e => e.Height) + .HasMaxLength(30) + .HasDefaultValue("") + .HasComment("高度") + .HasColumnName("height"); + entity.Property(e => e.Effect) + .HasDefaultValue((byte)0) + .HasComment("特效: 0无 1缩放动画") + .HasColumnName("effect"); + entity.Property(e => e.Title) + .HasMaxLength(255) + .HasComment("标题") + .HasColumnName("title"); + entity.Property(e => e.ImageDetails) + .HasMaxLength(255) + .HasComment("详情图片URL") + .HasColumnName("image_details"); + entity.Property(e => e.ImageBj) + .HasMaxLength(255) + .HasComment("背景图片URL") + .HasColumnName("image_bj"); + entity.Property(e => e.ImageDetailsX) + .HasMaxLength(255) + .HasComment("详情图片X偏移") + .HasColumnName("image_details_x"); + entity.Property(e => e.ImageDetailsY) + .HasMaxLength(255) + .HasComment("详情图片Y偏移") + .HasColumnName("image_details_y"); + entity.Property(e => e.ImageDetailsW) + .HasMaxLength(255) + .HasComment("详情图片宽度") + .HasColumnName("image_details_w"); + entity.Property(e => e.ImageDetailsH) + .HasMaxLength(255) + .HasComment("详情图片高度") + .HasColumnName("image_details_h"); + entity.Property(e => e.CreatedAt) + .HasDefaultValueSql("(getdate())") + .HasComment("创建时间") + .HasColumnName("created_at"); + entity.Property(e => e.UpdatedAt) + .HasDefaultValueSql("(getdate())") + .HasComment("更新时间") + .HasColumnName("updated_at"); + }); + OnModelCreatingPartial(modelBuilder); } diff --git a/server/C#/HoneyBox/src/HoneyBox.Model/Entities/FloatBallConfig.cs b/server/C#/HoneyBox/src/HoneyBox.Model/Entities/FloatBallConfig.cs new file mode 100644 index 00000000..2eb6a5a6 --- /dev/null +++ b/server/C#/HoneyBox/src/HoneyBox.Model/Entities/FloatBallConfig.cs @@ -0,0 +1,104 @@ +using System; + +namespace HoneyBox.Model.Entities; + +/// +/// 悬浮球配置表,存储首页悬浮球配置信息 +/// +public partial class FloatBallConfig +{ + /// + /// 主键ID + /// + public int Id { get; set; } + + /// + /// 状态: 0关闭 1开启 + /// + public byte Status { get; set; } + + /// + /// 类型: 1展示图片 2跳转页面 + /// + public byte Type { get; set; } + + /// + /// 悬浮球图片URL + /// + public string Image { get; set; } = string.Empty; + + /// + /// 跳转链接 + /// + public string LinkUrl { get; set; } = string.Empty; + + /// + /// X轴位置 + /// + public string PositionX { get; set; } = string.Empty; + + /// + /// Y轴位置 + /// + public string PositionY { get; set; } = string.Empty; + + /// + /// 宽度 + /// + public string Width { get; set; } = string.Empty; + + /// + /// 高度 + /// + public string Height { get; set; } = string.Empty; + + /// + /// 特效: 0无 1缩放动画 + /// + public byte Effect { get; set; } + + /// + /// 标题 + /// + public string? Title { get; set; } + + /// + /// 详情图片URL + /// + public string? ImageDetails { get; set; } + + /// + /// 背景图片URL + /// + public string? ImageBj { get; set; } + + /// + /// 详情图片X偏移 + /// + public string? ImageDetailsX { get; set; } + + /// + /// 详情图片Y偏移 + /// + public string? ImageDetailsY { get; set; } + + /// + /// 详情图片宽度 + /// + public string? ImageDetailsW { get; set; } + + /// + /// 详情图片高度 + /// + public string? ImageDetailsH { get; set; } + + /// + /// 创建时间 + /// + public DateTime CreatedAt { get; set; } + + /// + /// 更新时间 + /// + public DateTime UpdatedAt { get; set; } +} diff --git a/server/C#/HoneyBox/src/HoneyBox.Model/Models/FloatBall/FloatBallResponse.cs b/server/C#/HoneyBox/src/HoneyBox.Model/Models/FloatBall/FloatBallResponse.cs new file mode 100644 index 00000000..f2305d5c --- /dev/null +++ b/server/C#/HoneyBox/src/HoneyBox.Model/Models/FloatBall/FloatBallResponse.cs @@ -0,0 +1,106 @@ +using System.Text.Json.Serialization; + +namespace HoneyBox.Model.Models.FloatBall; + +/// +/// 悬浮球配置响应DTO +/// 排除 status, created_at, updated_at 字段 +/// +public class FloatBallResponse +{ + /// + /// 主键ID + /// + [JsonPropertyName("id")] + public int Id { get; set; } + + /// + /// 类型: 1展示图片 2跳转页面 + /// + [JsonPropertyName("type")] + public int Type { get; set; } + + /// + /// 悬浮球图片URL + /// + [JsonPropertyName("image")] + public string Image { get; set; } = string.Empty; + + /// + /// 跳转链接 + /// + [JsonPropertyName("link_url")] + public string LinkUrl { get; set; } = string.Empty; + + /// + /// X轴位置 + /// + [JsonPropertyName("position_x")] + public string PositionX { get; set; } = string.Empty; + + /// + /// Y轴位置 + /// + [JsonPropertyName("position_y")] + public string PositionY { get; set; } = string.Empty; + + /// + /// 宽度 + /// + [JsonPropertyName("width")] + public string Width { get; set; } = string.Empty; + + /// + /// 高度 + /// + [JsonPropertyName("height")] + public string Height { get; set; } = string.Empty; + + /// + /// 特效: 0无 1缩放动画 + /// + [JsonPropertyName("effect")] + public int Effect { get; set; } + + /// + /// 标题 + /// + [JsonPropertyName("title")] + public string? Title { get; set; } + + /// + /// 详情图片URL + /// + [JsonPropertyName("image_details")] + public string? ImageDetails { get; set; } + + /// + /// 背景图片URL + /// + [JsonPropertyName("image_bj")] + public string? ImageBj { get; set; } + + /// + /// 详情图片X偏移 + /// + [JsonPropertyName("image_details_x")] + public string? ImageDetailsX { get; set; } + + /// + /// 详情图片Y偏移 + /// + [JsonPropertyName("image_details_y")] + public string? ImageDetailsY { get; set; } + + /// + /// 详情图片宽度 + /// + [JsonPropertyName("image_details_w")] + public string? ImageDetailsW { get; set; } + + /// + /// 详情图片高度 + /// + [JsonPropertyName("image_details_h")] + public string? ImageDetailsH { get; set; } +} diff --git a/server/scripts/migrate_float_ball.js b/server/scripts/migrate_float_ball.js new file mode 100644 index 00000000..95aaee9b --- /dev/null +++ b/server/scripts/migrate_float_ball.js @@ -0,0 +1,267 @@ +/** + * 悬浮球配置数据迁移脚本 - Node.js + * Feature: float-ball-migration, Property 1: Data Migration Record Count Consistency + * Feature: float-ball-migration, Property 2: Data Migration ID Preservation + * Feature: float-ball-migration, Property 3: Timestamp Transformation Validity + * Feature: float-ball-migration, Property 8: Incremental Migration Idempotence + * Validates: Requirements 2.1, 2.2, 2.3, 2.5, 2.6 + * + * 源表: MySQL float_ball_config + * 目标表: SQL Server float_ball_configs + */ + +const mysql = require('mysql2/promise'); +const sql = require('mssql'); + +// MySQL 配置 +const mysqlConfig = { + host: '192.168.195.16', + port: 1887, + user: 'root', + password: 'Dbt@com@123', + database: 'youdas', + charset: 'utf8mb4' +}; + +// SQL Server 配置 +const sqlServerConfig = { + server: '192.168.195.15', + port: 1433, + user: 'sa', + password: 'Dbt@com@123', + database: 'honey_box', + options: { + encrypt: false, + trustServerCertificate: true + } +}; + +// Unix时间戳转换为 SQL Server DATETIME2 格式 +function unixToDatetime(timestamp) { + if (!timestamp || timestamp === 0) { + return null; + } + const date = new Date(timestamp * 1000); + return date.toISOString().slice(0, 23).replace('T', ' '); +} + +// 转义SQL字符串 +function escapeString(str) { + if (str === null || str === undefined) return 'NULL'; + return "N'" + String(str).replace(/'/g, "''") + "'"; +} + +// 格式化日期时间 +function formatDatetime(dt) { + if (!dt) return 'NULL'; + return "'" + dt + "'"; +} + + +// 获取已迁移的悬浮球配置ID列表 +async function getMigratedIds(pool) { + const result = await pool.request().query('SELECT id FROM float_ball_configs'); + return new Set(result.recordset.map(r => r.id)); +} + +// 批量插入悬浮球配置数据 +async function insertFloatBallsBatch(pool, floatBalls) { + if (floatBalls.length === 0) return 0; + + let insertedCount = 0; + + // 构建批量插入SQL + let sqlBatch = 'SET IDENTITY_INSERT float_ball_configs ON;\n'; + + for (const fb of floatBalls) { + const createdAt = unixToDatetime(fb.create_time) || new Date().toISOString().slice(0, 23).replace('T', ' '); + const updatedAt = unixToDatetime(fb.update_time) || createdAt; + + sqlBatch += ` +INSERT INTO float_ball_configs ( + id, status, type, image, link_url, position_x, position_y, width, height, effect, + title, image_details, image_bj, image_details_x, image_details_y, image_details_w, image_details_h, + created_at, updated_at +) VALUES ( + ${fb.id}, + ${fb.status || 0}, + ${fb.type || 1}, + ${escapeString(fb.image || '')}, + ${escapeString(fb.link_url || '')}, + ${escapeString(fb.position_x || '')}, + ${escapeString(fb.position_y || '')}, + ${escapeString(fb.width || '')}, + ${escapeString(fb.height || '')}, + ${fb.effect || 0}, + ${escapeString(fb.title)}, + ${escapeString(fb.image_details)}, + ${escapeString(fb.image_bj)}, + ${escapeString(fb.image_details_x)}, + ${escapeString(fb.image_details_y)}, + ${escapeString(fb.image_details_w)}, + ${escapeString(fb.image_details_h)}, + ${formatDatetime(createdAt)}, + ${formatDatetime(updatedAt)} +); +`; + } + + sqlBatch += 'SET IDENTITY_INSERT float_ball_configs OFF;'; + + try { + await pool.request().batch(sqlBatch); + insertedCount = floatBalls.length; + } catch (err) { + console.error('批量插入失败:', err.message); + // 如果批量失败,尝试逐条插入 + for (const fb of floatBalls) { + try { + const createdAt = unixToDatetime(fb.create_time) || new Date().toISOString().slice(0, 23).replace('T', ' '); + const updatedAt = unixToDatetime(fb.update_time) || createdAt; + + const singleSql = ` +SET IDENTITY_INSERT float_ball_configs ON; +INSERT INTO float_ball_configs ( + id, status, type, image, link_url, position_x, position_y, width, height, effect, + title, image_details, image_bj, image_details_x, image_details_y, image_details_w, image_details_h, + created_at, updated_at +) VALUES ( + ${fb.id}, + ${fb.status || 0}, + ${fb.type || 1}, + ${escapeString(fb.image || '')}, + ${escapeString(fb.link_url || '')}, + ${escapeString(fb.position_x || '')}, + ${escapeString(fb.position_y || '')}, + ${escapeString(fb.width || '')}, + ${escapeString(fb.height || '')}, + ${fb.effect || 0}, + ${escapeString(fb.title)}, + ${escapeString(fb.image_details)}, + ${escapeString(fb.image_bj)}, + ${escapeString(fb.image_details_x)}, + ${escapeString(fb.image_details_y)}, + ${escapeString(fb.image_details_w)}, + ${escapeString(fb.image_details_h)}, + ${formatDatetime(createdAt)}, + ${formatDatetime(updatedAt)} +); +SET IDENTITY_INSERT float_ball_configs OFF;`; + + await pool.request().batch(singleSql); + insertedCount++; + console.log(` ✓ 插入悬浮球配置 ${fb.id} 成功`); + } catch (singleErr) { + console.error(` ✗ 插入悬浮球配置 ${fb.id} 失败:`, singleErr.message); + } + } + } + + return insertedCount; +} + + +async function main() { + console.log('========================================'); + console.log('悬浮球配置数据迁移脚本 - Node.js'); + console.log('========================================\n'); + + let mysqlConn = null; + let sqlPool = null; + + try { + // 连接 MySQL + console.log('正在连接 MySQL...'); + mysqlConn = await mysql.createConnection(mysqlConfig); + console.log('MySQL 连接成功\n'); + + // 连接 SQL Server + console.log('正在连接 SQL Server...'); + sqlPool = await sql.connect(sqlServerConfig); + console.log('SQL Server 连接成功\n'); + + // 获取已迁移的ID(支持增量迁移) + console.log('正在获取已迁移的悬浮球配置ID...'); + const migratedIds = await getMigratedIds(sqlPool); + console.log(`已迁移悬浮球配置数: ${migratedIds.size}\n`); + + // 从 MySQL 获取所有悬浮球配置数据 + console.log('正在从 MySQL 读取悬浮球配置数据...'); + const [rows] = await mysqlConn.execute(` + SELECT id, status, type, image, link_url, position_x, position_y, width, height, effect, + create_time, update_time, title, image_details, image_bj, + image_details_x, image_details_y, image_details_w, image_details_h + FROM float_ball_config + ORDER BY id + `); + console.log(`MySQL 悬浮球配置总数: ${rows.length}\n`); + + // 过滤出未迁移的配置(增量迁移) + const floatBallsToMigrate = rows.filter(fb => !migratedIds.has(fb.id)); + console.log(`待迁移悬浮球配置数: ${floatBallsToMigrate.length}\n`); + + if (floatBallsToMigrate.length === 0) { + console.log('所有悬浮球配置数据已迁移完成!'); + } else { + // 批量迁移(每批50条) + const batchSize = 50; + let totalInserted = 0; + + for (let i = 0; i < floatBallsToMigrate.length; i += batchSize) { + const batch = floatBallsToMigrate.slice(i, i + batchSize); + const inserted = await insertFloatBallsBatch(sqlPool, batch); + totalInserted += inserted; + console.log(`进度: ${Math.min(i + batchSize, floatBallsToMigrate.length)}/${floatBallsToMigrate.length} (本批插入: ${inserted})`); + } + + console.log(`\n迁移完成!共插入 ${totalInserted} 条记录`); + } + + // 验证迁移结果 + console.log('\n========================================'); + console.log('迁移结果验证'); + console.log('========================================'); + + const [mysqlCount] = await mysqlConn.execute('SELECT COUNT(*) as count FROM float_ball_config'); + const sqlResult = await sqlPool.request().query('SELECT COUNT(*) as count FROM float_ball_configs'); + + console.log(`MySQL float_ball_config 表记录数: ${mysqlCount[0].count}`); + console.log(`SQL Server float_ball_configs 表记录数: ${sqlResult.recordset[0].count}`); + + if (mysqlCount[0].count === sqlResult.recordset[0].count) { + console.log('\n✅ 数据迁移完成,记录数一致!'); + } else { + console.log(`\n⚠️ 记录数不一致,差异: ${mysqlCount[0].count - sqlResult.recordset[0].count}`); + } + + // 验证ID保留 + console.log('\n验证ID保留...'); + const [mysqlIds] = await mysqlConn.execute('SELECT id FROM float_ball_config ORDER BY id'); + const sqlIds = await sqlPool.request().query('SELECT id FROM float_ball_configs ORDER BY id'); + + const mysqlIdSet = new Set(mysqlIds.map(r => r.id)); + const sqlIdSet = new Set(sqlIds.recordset.map(r => r.id)); + + let idMismatch = false; + for (const id of mysqlIdSet) { + if (!sqlIdSet.has(id)) { + console.log(` ⚠️ MySQL ID ${id} 未在 SQL Server 中找到`); + idMismatch = true; + } + } + + if (!idMismatch) { + console.log('✅ 所有ID已正确保留!'); + } + + } catch (err) { + console.error('迁移过程中发生错误:', err); + process.exit(1); + } finally { + // 关闭连接 + if (mysqlConn) await mysqlConn.end(); + if (sqlPool) await sqlPool.close(); + } +} + +main();