This commit is contained in:
zpc 2026-01-03 21:06:26 +08:00
parent 68955dcd80
commit 43f8a88394
16 changed files with 1268 additions and 66 deletions

View File

@ -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<List<FloatBallResponse>> GetEnabledFloatBallsAsync();
}
```
### 5. API Endpoint
```
GET /api/getFloatBall
Authorization: None (AllowAnonymous)
Response: ApiResponse<List<FloatBallResponse>>
```
## 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}**

View File

@ -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)

View File

@ -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<FloatBallConfig>`
- 配置 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]` 属性

View File

@ -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",

View File

@ -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",

View File

@ -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;
/// 配置控制器 - 处理系统配置和平台配置
/// </summary>
/// <remarks>
/// 提供系统配置、商品类型、平台配置等功能
/// 提供系统配置、商品类型、平台配置、悬浮球配置等功能
/// </remarks>
[ApiController]
[Route("api")]
public class ConfigController : ControllerBase
{
private readonly IConfigService _configService;
private readonly IFloatBallService _floatBallService;
private readonly ILogger<ConfigController> _logger;
public ConfigController(
IConfigService configService,
IFloatBallService floatBallService,
ILogger<ConfigController> logger)
{
_configService = configService;
_floatBallService = floatBallService;
_logger = logger;
}
@ -160,9 +165,9 @@ public class ConfigController : ControllerBase
/// </remarks>
/// <param name="request">请求参数</param>
/// <returns>单页内容数据</returns>
[HttpPost("danye")]
[HttpGet("danye")]
[ProducesResponseType(typeof(ApiResponse<DanyeContentDto>), StatusCodes.Status200OK)]
public async Task<ApiResponse<DanyeContentDto>> GetDanyeContent([FromBody] DanyeRequest? request)
public async Task<ApiResponse<DanyeContentDto>> GetDanyeContent([FromQuery] DanyeRequest? request)
{
try
{
@ -184,4 +189,32 @@ public class ConfigController : ControllerBase
return ApiResponse<DanyeContentDto>.Fail("获取单页内容失败");
}
}
/// <summary>
/// 获取悬浮球配置
/// </summary>
/// <remarks>
/// GET /api/getFloatBall
///
/// 返回所有启用的悬浮球配置列表
/// 支持未登录访问
/// Requirements: 3.1, 3.2, 3.3, 3.4, 3.5, 3.6, 5.1, 5.2, 5.3
/// </remarks>
/// <returns>悬浮球配置列表</returns>
[HttpGet("getFloatBall")]
[AllowAnonymous]
[ProducesResponseType(typeof(ApiResponse<List<FloatBallResponse>>), StatusCodes.Status200OK)]
public async Task<ApiResponse<List<FloatBallResponse>>> GetFloatBall()
{
try
{
var result = await _floatBallService.GetEnabledFloatBallsAsync();
return ApiResponse<List<FloatBallResponse>>.Success(result, "获取悬浮球配置成功");
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to get float ball configurations");
return ApiResponse<List<FloatBallResponse>>.Fail("获取悬浮球配置失败");
}
}
}

View File

@ -1,4 +1,4 @@
using System.Security.Claims;
using System.Security.Claims;
using HoneyBox.Core.Interfaces;
using HoneyBox.Model.Base;
using HoneyBox.Model.Models;

View File

@ -0,0 +1,15 @@
using HoneyBox.Model.Models.FloatBall;
namespace HoneyBox.Core.Interfaces;
/// <summary>
/// 悬浮球服务接口
/// </summary>
public interface IFloatBallService
{
/// <summary>
/// 获取所有启用的悬浮球配置
/// </summary>
/// <returns>启用的悬浮球配置列表</returns>
Task<List<FloatBallResponse>> GetEnabledFloatBallsAsync();
}

View File

@ -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;
/// <summary>
/// 悬浮球服务实现
/// </summary>
public class FloatBallService : IFloatBallService
{
private readonly HoneyBoxDbContext _dbContext;
private readonly ILogger<FloatBallService> _logger;
public FloatBallService(
HoneyBoxDbContext dbContext,
ILogger<FloatBallService> logger)
{
_dbContext = dbContext;
_logger = logger;
}
/// <inheritdoc />
public async Task<List<FloatBallResponse>> 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;
}
}
}

View File

@ -277,6 +277,14 @@ public class ServiceModule : Module
return new ConfigService(dbContext, logger, redisService);
}).As<IConfigService>().InstancePerLifetimeScope();
// 注册悬浮球服务
builder.Register(c =>
{
var dbContext = c.Resolve<HoneyBoxDbContext>();
var logger = c.Resolve<ILogger<FloatBallService>>();
return new FloatBallService(dbContext, logger);
}).As<IFloatBallService>().InstancePerLifetimeScope();
// ========== 签到系统服务注册 ==========
// 注册签到服务

View File

@ -108,6 +108,8 @@ public partial class HoneyBoxDbContext : DbContext
public virtual DbSet<Reward> Rewards { get; set; }
public virtual DbSet<FloatBallConfig> 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<FloatBallConfig>(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);
}

View File

@ -0,0 +1,104 @@
using System;
namespace HoneyBox.Model.Entities;
/// <summary>
/// 悬浮球配置表,存储首页悬浮球配置信息
/// </summary>
public partial class FloatBallConfig
{
/// <summary>
/// 主键ID
/// </summary>
public int Id { get; set; }
/// <summary>
/// 状态: 0关闭 1开启
/// </summary>
public byte Status { get; set; }
/// <summary>
/// 类型: 1展示图片 2跳转页面
/// </summary>
public byte Type { get; set; }
/// <summary>
/// 悬浮球图片URL
/// </summary>
public string Image { get; set; } = string.Empty;
/// <summary>
/// 跳转链接
/// </summary>
public string LinkUrl { get; set; } = string.Empty;
/// <summary>
/// X轴位置
/// </summary>
public string PositionX { get; set; } = string.Empty;
/// <summary>
/// Y轴位置
/// </summary>
public string PositionY { get; set; } = string.Empty;
/// <summary>
/// 宽度
/// </summary>
public string Width { get; set; } = string.Empty;
/// <summary>
/// 高度
/// </summary>
public string Height { get; set; } = string.Empty;
/// <summary>
/// 特效: 0无 1缩放动画
/// </summary>
public byte Effect { get; set; }
/// <summary>
/// 标题
/// </summary>
public string? Title { get; set; }
/// <summary>
/// 详情图片URL
/// </summary>
public string? ImageDetails { get; set; }
/// <summary>
/// 背景图片URL
/// </summary>
public string? ImageBj { get; set; }
/// <summary>
/// 详情图片X偏移
/// </summary>
public string? ImageDetailsX { get; set; }
/// <summary>
/// 详情图片Y偏移
/// </summary>
public string? ImageDetailsY { get; set; }
/// <summary>
/// 详情图片宽度
/// </summary>
public string? ImageDetailsW { get; set; }
/// <summary>
/// 详情图片高度
/// </summary>
public string? ImageDetailsH { get; set; }
/// <summary>
/// 创建时间
/// </summary>
public DateTime CreatedAt { get; set; }
/// <summary>
/// 更新时间
/// </summary>
public DateTime UpdatedAt { get; set; }
}

View File

@ -0,0 +1,106 @@
using System.Text.Json.Serialization;
namespace HoneyBox.Model.Models.FloatBall;
/// <summary>
/// 悬浮球配置响应DTO
/// 排除 status, created_at, updated_at 字段
/// </summary>
public class FloatBallResponse
{
/// <summary>
/// 主键ID
/// </summary>
[JsonPropertyName("id")]
public int Id { get; set; }
/// <summary>
/// 类型: 1展示图片 2跳转页面
/// </summary>
[JsonPropertyName("type")]
public int Type { get; set; }
/// <summary>
/// 悬浮球图片URL
/// </summary>
[JsonPropertyName("image")]
public string Image { get; set; } = string.Empty;
/// <summary>
/// 跳转链接
/// </summary>
[JsonPropertyName("link_url")]
public string LinkUrl { get; set; } = string.Empty;
/// <summary>
/// X轴位置
/// </summary>
[JsonPropertyName("position_x")]
public string PositionX { get; set; } = string.Empty;
/// <summary>
/// Y轴位置
/// </summary>
[JsonPropertyName("position_y")]
public string PositionY { get; set; } = string.Empty;
/// <summary>
/// 宽度
/// </summary>
[JsonPropertyName("width")]
public string Width { get; set; } = string.Empty;
/// <summary>
/// 高度
/// </summary>
[JsonPropertyName("height")]
public string Height { get; set; } = string.Empty;
/// <summary>
/// 特效: 0无 1缩放动画
/// </summary>
[JsonPropertyName("effect")]
public int Effect { get; set; }
/// <summary>
/// 标题
/// </summary>
[JsonPropertyName("title")]
public string? Title { get; set; }
/// <summary>
/// 详情图片URL
/// </summary>
[JsonPropertyName("image_details")]
public string? ImageDetails { get; set; }
/// <summary>
/// 背景图片URL
/// </summary>
[JsonPropertyName("image_bj")]
public string? ImageBj { get; set; }
/// <summary>
/// 详情图片X偏移
/// </summary>
[JsonPropertyName("image_details_x")]
public string? ImageDetailsX { get; set; }
/// <summary>
/// 详情图片Y偏移
/// </summary>
[JsonPropertyName("image_details_y")]
public string? ImageDetailsY { get; set; }
/// <summary>
/// 详情图片宽度
/// </summary>
[JsonPropertyName("image_details_w")]
public string? ImageDetailsW { get; set; }
/// <summary>
/// 详情图片高度
/// </summary>
[JsonPropertyName("image_details_h")]
public string? ImageDetailsH { get; set; }
}

View File

@ -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();