Compare commits
78 Commits
4142b621c3
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| cbdee5068a | |||
| 685a8aeaec | |||
| 766be485d0 | |||
| faf40d6e27 | |||
| 57e40440ab | |||
| 4bb03aa0df | |||
| de9a0c2978 | |||
| 195792ec66 | |||
| a8b19cbacc | |||
| a64d1b459a | |||
| f7f505d109 | |||
| 1d467a5fc4 | |||
| 24059dfebb | |||
| edc0122a3b | |||
| 593f95ffaa | |||
| 0b829b72cb | |||
| 2deb0279bb | |||
| 2b444abe10 | |||
| 32612ca6d4 | |||
| 7f78795135 | |||
| 91752baa3b | |||
| a04b4be7e5 | |||
| f8874ff60c | |||
| 88e5fd23be | |||
| 12d488a5ca | |||
| 519428c52d | |||
| 72c7a38f1d | |||
| 5700da58d9 | |||
| bfdfad14d8 | |||
| cc2276fe27 | |||
| 7c53adc804 | |||
| b95c5029f1 | |||
| 1285ac4648 | |||
| 0227f06658 | |||
| 07614b5fe6 | |||
| eb4f09e391 | |||
| 87bc5c0e0c | |||
| b822489111 | |||
| 2ca2a4c114 | |||
| c43517969d | |||
| 85a58123be | |||
| ab09c24338 | |||
| 6ec47220c2 | |||
| 6985a4af42 | |||
| fa1acec8af | |||
| c342f543f7 | |||
| db6aaaec84 | |||
| 5481c091f2 | |||
| 2fcff63ee2 | |||
| ddf2b6316d | |||
| a6db06c6cb | |||
| 6b09006071 | |||
| c0bd6b8200 | |||
| bdbef21fa8 | |||
| bdaa6a0dc8 | |||
| 45b5e6f71e | |||
| 4f33d81aa3 | |||
| 4579fefef9 | |||
| d64b68abdd | |||
| f29802eb5a | |||
| c5db5d773b | |||
| 21051dd199 | |||
| 1eb986177d | |||
| 8b773cbfe1 | |||
| 3c3453668f | |||
| 4fb861d9e6 | |||
| 00fa7a21f6 | |||
| bf333cd718 | |||
| 28a0c3cfe4 | |||
| 16ad80f2a8 | |||
| 2d3fceb265 | |||
| d9933e4537 | |||
| 6ece22d23d | |||
| 8e17fef32f | |||
| f2f7ec9100 | |||
| dbf77c7d50 | |||
| 94799ee189 | |||
| eb1a677612 |
10
.editorconfig
Normal file
10
.editorconfig
Normal file
@ -0,0 +1,10 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = crlf
|
||||
insert_final_newline = true
|
||||
|
||||
[*.cs]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
125
AdminMenu_RouteSeed.sql
Normal file
125
AdminMenu_RouteSeed.sql
Normal file
@ -0,0 +1,125 @@
|
||||
SET @now = NOW();
|
||||
SET @createdBy = 'System';
|
||||
SET @shortSnowflakeEpoch = 1577836800000;
|
||||
SET @shortSnowflakeTimestamp = CAST(UNIX_TIMESTAMP(CURRENT_TIMESTAMP(3)) * 1000 AS UNSIGNED) - @shortSnowflakeEpoch;
|
||||
SET @shortSnowflakeSequence = -1;
|
||||
|
||||
SET @ai_config_id = COALESCE((SELECT `Id` FROM `System_AdminMenu` WHERE `Code` = 'ai-config' LIMIT 1), (@shortSnowflakeTimestamp * 1000 + (@shortSnowflakeSequence := @shortSnowflakeSequence + 1)));
|
||||
INSERT INTO `System_AdminMenu` (`Id`, `ParentId`, `Name`, `Code`, `Path`, `Component`, `Icon`, `Sort`, `IsVisible`, `Status`, `IsDeleted`, `CreatedBy`, `CreatedAt`, `UpdatedBy`, `UpdatedAt`)
|
||||
VALUES (@ai_config_id, 0, 'AI配置中心', 'ai-config', '/ai-config', 'layout.base', 'ri:robot-line', 0, b'1', 1, b'0', @createdBy, @now, @createdBy, @now)
|
||||
ON DUPLICATE KEY UPDATE `ParentId` = VALUES(`ParentId`), `Name` = VALUES(`Name`), `Path` = VALUES(`Path`), `Component` = VALUES(`Component`), `Icon` = VALUES(`Icon`), `Sort` = VALUES(`Sort`), `IsVisible` = VALUES(`IsVisible`), `Status` = VALUES(`Status`), `IsDeleted` = b'0', `UpdatedBy` = @createdBy, `UpdatedAt` = @now;
|
||||
|
||||
SET @ai_config_base_prompt_id = COALESCE((SELECT `Id` FROM `System_AdminMenu` WHERE `Code` = 'ai-config:base-prompt' LIMIT 1), (@shortSnowflakeTimestamp * 1000 + (@shortSnowflakeSequence := @shortSnowflakeSequence + 1)));
|
||||
INSERT INTO `System_AdminMenu` (`Id`, `ParentId`, `Name`, `Code`, `Path`, `Component`, `Icon`, `Sort`, `IsVisible`, `Status`, `IsDeleted`, `CreatedBy`, `CreatedAt`, `UpdatedBy`, `UpdatedAt`)
|
||||
VALUES (@ai_config_base_prompt_id, @ai_config_id, '基础提示词配置', 'ai-config:base-prompt', '/ai-config/base-prompt', 'view.ai-config_base-prompt', 'ri:file-text-line', 0, b'1', 1, b'0', @createdBy, @now, @createdBy, @now)
|
||||
ON DUPLICATE KEY UPDATE `ParentId` = VALUES(`ParentId`), `Name` = VALUES(`Name`), `Path` = VALUES(`Path`), `Component` = VALUES(`Component`), `Icon` = VALUES(`Icon`), `Sort` = VALUES(`Sort`), `IsVisible` = VALUES(`IsVisible`), `Status` = VALUES(`Status`), `IsDeleted` = b'0', `UpdatedBy` = @createdBy, `UpdatedAt` = @now;
|
||||
|
||||
SET @main_nav_id = COALESCE((SELECT `Id` FROM `System_AdminMenu` WHERE `Code` = 'main-nav' LIMIT 1), (@shortSnowflakeTimestamp * 1000 + (@shortSnowflakeSequence := @shortSnowflakeSequence + 1)));
|
||||
INSERT INTO `System_AdminMenu` (`Id`, `ParentId`, `Name`, `Code`, `Path`, `Component`, `Icon`, `Sort`, `IsVisible`, `Status`, `IsDeleted`, `CreatedBy`, `CreatedAt`, `UpdatedBy`, `UpdatedAt`)
|
||||
VALUES (@main_nav_id, 0, '主导航', 'main-nav', '/main-nav', 'layout.base', 'ri:organization-chart', 1, b'1', 1, b'0', @createdBy, @now, @createdBy, @now)
|
||||
ON DUPLICATE KEY UPDATE `ParentId` = VALUES(`ParentId`), `Name` = VALUES(`Name`), `Path` = VALUES(`Path`), `Component` = VALUES(`Component`), `Icon` = VALUES(`Icon`), `Sort` = VALUES(`Sort`), `IsVisible` = VALUES(`IsVisible`), `Status` = VALUES(`Status`), `IsDeleted` = b'0', `UpdatedBy` = @createdBy, `UpdatedAt` = @now;
|
||||
|
||||
SET @main_nav_home_id = COALESCE((SELECT `Id` FROM `System_AdminMenu` WHERE `Code` = 'main-nav:home' LIMIT 1), (@shortSnowflakeTimestamp * 1000 + (@shortSnowflakeSequence := @shortSnowflakeSequence + 1)));
|
||||
INSERT INTO `System_AdminMenu` (`Id`, `ParentId`, `Name`, `Code`, `Path`, `Component`, `Icon`, `Sort`, `IsVisible`, `Status`, `IsDeleted`, `CreatedBy`, `CreatedAt`, `UpdatedBy`, `UpdatedAt`)
|
||||
VALUES (@main_nav_home_id, @main_nav_id, '仪表盘', 'main-nav:home', '/main-nav/home', 'view.main-nav_home', 'ri:home-2-line', 1, b'1', 1, b'0', @createdBy, @now, @createdBy, @now)
|
||||
ON DUPLICATE KEY UPDATE `ParentId` = VALUES(`ParentId`), `Name` = VALUES(`Name`), `Path` = VALUES(`Path`), `Component` = VALUES(`Component`), `Icon` = VALUES(`Icon`), `Sort` = VALUES(`Sort`), `IsVisible` = VALUES(`IsVisible`), `Status` = VALUES(`Status`), `IsDeleted` = b'0', `UpdatedBy` = @createdBy, `UpdatedAt` = @now;
|
||||
|
||||
SET @main_nav_journal_qr_code_id = COALESCE((SELECT `Id` FROM `System_AdminMenu` WHERE `Code` = 'main-nav:journal-qr-code' LIMIT 1), (@shortSnowflakeTimestamp * 1000 + (@shortSnowflakeSequence := @shortSnowflakeSequence + 1)));
|
||||
INSERT INTO `System_AdminMenu` (`Id`, `ParentId`, `Name`, `Code`, `Path`, `Component`, `Icon`, `Sort`, `IsVisible`, `Status`, `IsDeleted`, `CreatedBy`, `CreatedAt`, `UpdatedBy`, `UpdatedAt`)
|
||||
VALUES (@main_nav_journal_qr_code_id, @main_nav_id, '期刊二维码管理', 'main-nav:journal-qr-code', '/main-nav/journal-qr-code', 'view.main-nav_journal-qr-code', 'ri:qr-code-line', 2, b'1', 1, b'0', @createdBy, @now, @createdBy, @now)
|
||||
ON DUPLICATE KEY UPDATE `ParentId` = VALUES(`ParentId`), `Name` = VALUES(`Name`), `Path` = VALUES(`Path`), `Component` = VALUES(`Component`), `Icon` = VALUES(`Icon`), `Sort` = VALUES(`Sort`), `IsVisible` = VALUES(`IsVisible`), `Status` = VALUES(`Status`), `IsDeleted` = b'0', `UpdatedBy` = @createdBy, `UpdatedAt` = @now;
|
||||
|
||||
SET @main_nav_book_management_id = COALESCE((SELECT `Id` FROM `System_AdminMenu` WHERE `Code` = 'main-nav:book-management' LIMIT 1), (@shortSnowflakeTimestamp * 1000 + (@shortSnowflakeSequence := @shortSnowflakeSequence + 1)));
|
||||
INSERT INTO `System_AdminMenu` (`Id`, `ParentId`, `Name`, `Code`, `Path`, `Component`, `Icon`, `Sort`, `IsVisible`, `Status`, `IsDeleted`, `CreatedBy`, `CreatedAt`, `UpdatedBy`, `UpdatedAt`)
|
||||
VALUES (@main_nav_book_management_id, @main_nav_id, '书籍管理', 'main-nav:book-management', '/main-nav/book-management', 'ayout.base', 'ri:book-open-line', 5, b'1', 1, b'0', @createdBy, @now, @createdBy, @now)
|
||||
ON DUPLICATE KEY UPDATE `ParentId` = VALUES(`ParentId`), `Name` = VALUES(`Name`), `Path` = VALUES(`Path`), `Component` = VALUES(`Component`), `Icon` = VALUES(`Icon`), `Sort` = VALUES(`Sort`), `IsVisible` = VALUES(`IsVisible`), `Status` = VALUES(`Status`), `IsDeleted` = b'0', `UpdatedBy` = @createdBy, `UpdatedAt` = @now;
|
||||
|
||||
SET @main_nav_community_message_management_id = COALESCE((SELECT `Id` FROM `System_AdminMenu` WHERE `Code` = 'main-nav:community-message-management' LIMIT 1), (@shortSnowflakeTimestamp * 1000 + (@shortSnowflakeSequence := @shortSnowflakeSequence + 1)));
|
||||
INSERT INTO `System_AdminMenu` (`Id`, `ParentId`, `Name`, `Code`, `Path`, `Component`, `Icon`, `Sort`, `IsVisible`, `Status`, `IsDeleted`, `CreatedBy`, `CreatedAt`, `UpdatedBy`, `UpdatedAt`)
|
||||
VALUES (@main_nav_community_message_management_id, @main_nav_id, '社区消息管理', 'main-nav:community-message-management', '/main-nav/community-message-management', 'view.main-nav_community-message-management', 'ic:baseline-mark-unread-chat-alt', 6, b'1', 1, b'0', @createdBy, @now, @createdBy, @now)
|
||||
ON DUPLICATE KEY UPDATE `ParentId` = VALUES(`ParentId`), `Name` = VALUES(`Name`), `Path` = VALUES(`Path`), `Component` = VALUES(`Component`), `Icon` = VALUES(`Icon`), `Sort` = VALUES(`Sort`), `IsVisible` = VALUES(`IsVisible`), `Status` = VALUES(`Status`), `IsDeleted` = b'0', `UpdatedBy` = @createdBy, `UpdatedAt` = @now;
|
||||
|
||||
SET @main_nav_medal_management_id = COALESCE((SELECT `Id` FROM `System_AdminMenu` WHERE `Code` = 'main-nav:medal-management' LIMIT 1), (@shortSnowflakeTimestamp * 1000 + (@shortSnowflakeSequence := @shortSnowflakeSequence + 1)));
|
||||
INSERT INTO `System_AdminMenu` (`Id`, `ParentId`, `Name`, `Code`, `Path`, `Component`, `Icon`, `Sort`, `IsVisible`, `Status`, `IsDeleted`, `CreatedBy`, `CreatedAt`, `UpdatedBy`, `UpdatedAt`)
|
||||
VALUES (@main_nav_medal_management_id, @main_nav_id, '勋章管理', 'main-nav:medal-management', '/main-nav/medal-management', 'view.main-nav_medal-management', 'ri:medal-line', 7, b'1', 1, b'0', @createdBy, @now, @createdBy, @now)
|
||||
ON DUPLICATE KEY UPDATE `ParentId` = VALUES(`ParentId`), `Name` = VALUES(`Name`), `Path` = VALUES(`Path`), `Component` = VALUES(`Component`), `Icon` = VALUES(`Icon`), `Sort` = VALUES(`Sort`), `IsVisible` = VALUES(`IsVisible`), `Status` = VALUES(`Status`), `IsDeleted` = b'0', `UpdatedBy` = @createdBy, `UpdatedAt` = @now;
|
||||
|
||||
SET @main_nav_pet_management_id = COALESCE((SELECT `Id` FROM `System_AdminMenu` WHERE `Code` = 'main-nav:pet-management' LIMIT 1), (@shortSnowflakeTimestamp * 1000 + (@shortSnowflakeSequence := @shortSnowflakeSequence + 1)));
|
||||
INSERT INTO `System_AdminMenu` (`Id`, `ParentId`, `Name`, `Code`, `Path`, `Component`, `Icon`, `Sort`, `IsVisible`, `Status`, `IsDeleted`, `CreatedBy`, `CreatedAt`, `UpdatedBy`, `UpdatedAt`)
|
||||
VALUES (@main_nav_pet_management_id, @main_nav_id, '宠物管理', 'main-nav:pet-management', '/main-nav/pet-management', 'view.main-nav_pet-management', 'mdi:paw', 8, b'1', 1, b'0', @createdBy, @now, @createdBy, @now)
|
||||
ON DUPLICATE KEY UPDATE `ParentId` = VALUES(`ParentId`), `Name` = VALUES(`Name`), `Path` = VALUES(`Path`), `Component` = VALUES(`Component`), `Icon` = VALUES(`Icon`), `Sort` = VALUES(`Sort`), `IsVisible` = VALUES(`IsVisible`), `Status` = VALUES(`Status`), `IsDeleted` = b'0', `UpdatedBy` = @createdBy, `UpdatedAt` = @now;
|
||||
|
||||
SET @main_nav_product_management_id = COALESCE((SELECT `Id` FROM `System_AdminMenu` WHERE `Code` = 'main-nav:product-management' LIMIT 1), (@shortSnowflakeTimestamp * 1000 + (@shortSnowflakeSequence := @shortSnowflakeSequence + 1)));
|
||||
INSERT INTO `System_AdminMenu` (`Id`, `ParentId`, `Name`, `Code`, `Path`, `Component`, `Icon`, `Sort`, `IsVisible`, `Status`, `IsDeleted`, `CreatedBy`, `CreatedAt`, `UpdatedBy`, `UpdatedAt`)
|
||||
VALUES (@main_nav_product_management_id, @main_nav_id, '商品管理', 'main-nav:product-management', '/main-nav/product-management', 'view.main-nav_product-management', 'icon-park-solid:health-products', 9, b'1', 1, b'0', @createdBy, @now, @createdBy, @now)
|
||||
ON DUPLICATE KEY UPDATE `ParentId` = VALUES(`ParentId`), `Name` = VALUES(`Name`), `Path` = VALUES(`Path`), `Component` = VALUES(`Component`), `Icon` = VALUES(`Icon`), `Sort` = VALUES(`Sort`), `IsVisible` = VALUES(`IsVisible`), `Status` = VALUES(`Status`), `IsDeleted` = b'0', `UpdatedBy` = @createdBy, `UpdatedAt` = @now;
|
||||
|
||||
SET @main_nav_check_in_config_management_id = COALESCE((SELECT `Id` FROM `System_AdminMenu` WHERE `Code` = 'main-nav:check-in-config-management' LIMIT 1), (@shortSnowflakeTimestamp * 1000 + (@shortSnowflakeSequence := @shortSnowflakeSequence + 1)));
|
||||
INSERT INTO `System_AdminMenu` (`Id`, `ParentId`, `Name`, `Code`, `Path`, `Component`, `Icon`, `Sort`, `IsVisible`, `Status`, `IsDeleted`, `CreatedBy`, `CreatedAt`, `UpdatedBy`, `UpdatedAt`)
|
||||
VALUES (@main_nav_check_in_config_management_id, @main_nav_id, '签到配置管理', 'main-nav:check-in-config-management', '/main-nav/check-in-config-management', 'view.main-nav_check-in-config-management', 'ri:check-line', 10, b'1', 1, b'0', @createdBy, @now, @createdBy, @now)
|
||||
ON DUPLICATE KEY UPDATE `ParentId` = VALUES(`ParentId`), `Name` = VALUES(`Name`), `Path` = VALUES(`Path`), `Component` = VALUES(`Component`), `Icon` = VALUES(`Icon`), `Sort` = VALUES(`Sort`), `IsVisible` = VALUES(`IsVisible`), `Status` = VALUES(`Status`), `IsDeleted` = b'0', `UpdatedBy` = @createdBy, `UpdatedAt` = @now;
|
||||
|
||||
SET @data_statistics_id = COALESCE((SELECT `Id` FROM `System_AdminMenu` WHERE `Code` = 'data-statistics' LIMIT 1), (@shortSnowflakeTimestamp * 1000 + (@shortSnowflakeSequence := @shortSnowflakeSequence + 1)));
|
||||
INSERT INTO `System_AdminMenu` (`Id`, `ParentId`, `Name`, `Code`, `Path`, `Component`, `Icon`, `Sort`, `IsVisible`, `Status`, `IsDeleted`, `CreatedBy`, `CreatedAt`, `UpdatedBy`, `UpdatedAt`)
|
||||
VALUES (@data_statistics_id, 0, '数据统计', 'data-statistics', '/data-statistics', 'layout.base', 'ri:bar-chart-2-line', 2, b'1', 1, b'0', @createdBy, @now, @createdBy, @now)
|
||||
ON DUPLICATE KEY UPDATE `ParentId` = VALUES(`ParentId`), `Name` = VALUES(`Name`), `Path` = VALUES(`Path`), `Component` = VALUES(`Component`), `Icon` = VALUES(`Icon`), `Sort` = VALUES(`Sort`), `IsVisible` = VALUES(`IsVisible`), `Status` = VALUES(`Status`), `IsDeleted` = b'0', `UpdatedBy` = @createdBy, `UpdatedAt` = @now;
|
||||
|
||||
SET @data_statistics_statistics_id = COALESCE((SELECT `Id` FROM `System_AdminMenu` WHERE `Code` = 'data-statistics:statistics' LIMIT 1), (@shortSnowflakeTimestamp * 1000 + (@shortSnowflakeSequence := @shortSnowflakeSequence + 1)));
|
||||
INSERT INTO `System_AdminMenu` (`Id`, `ParentId`, `Name`, `Code`, `Path`, `Component`, `Icon`, `Sort`, `IsVisible`, `Status`, `IsDeleted`, `CreatedBy`, `CreatedAt`, `UpdatedBy`, `UpdatedAt`)
|
||||
VALUES (@data_statistics_statistics_id, @data_statistics_id, '数据统计', 'data-statistics:statistics', '/data-statistics/statistics', 'view.data-statistics_statistics', 'ri:bar-chart-2-line', 0, b'1', 1, b'0', @createdBy, @now, @createdBy, @now)
|
||||
ON DUPLICATE KEY UPDATE `ParentId` = VALUES(`ParentId`), `Name` = VALUES(`Name`), `Path` = VALUES(`Path`), `Component` = VALUES(`Component`), `Icon` = VALUES(`Icon`), `Sort` = VALUES(`Sort`), `IsVisible` = VALUES(`IsVisible`), `Status` = VALUES(`Status`), `IsDeleted` = b'0', `UpdatedBy` = @createdBy, `UpdatedAt` = @now;
|
||||
|
||||
SET @system_manage_id = COALESCE((SELECT `Id` FROM `System_AdminMenu` WHERE `Code` = 'system-manage' LIMIT 1), (@shortSnowflakeTimestamp * 1000 + (@shortSnowflakeSequence := @shortSnowflakeSequence + 1)));
|
||||
INSERT INTO `System_AdminMenu` (`Id`, `ParentId`, `Name`, `Code`, `Path`, `Component`, `Icon`, `Sort`, `IsVisible`, `Status`, `IsDeleted`, `CreatedBy`, `CreatedAt`, `UpdatedBy`, `UpdatedAt`)
|
||||
VALUES (@system_manage_id, 0, '系统管理', 'system-manage', '/system-manage', 'layout.base', 'ri:settings-2-line', 3, b'1', 1, b'0', @createdBy, @now, @createdBy, @now)
|
||||
ON DUPLICATE KEY UPDATE `ParentId` = VALUES(`ParentId`), `Name` = VALUES(`Name`), `Path` = VALUES(`Path`), `Component` = VALUES(`Component`), `Icon` = VALUES(`Icon`), `Sort` = VALUES(`Sort`), `IsVisible` = VALUES(`IsVisible`), `Status` = VALUES(`Status`), `IsDeleted` = b'0', `UpdatedBy` = @createdBy, `UpdatedAt` = @now;
|
||||
|
||||
SET @system_manage_menu_id = COALESCE((SELECT `Id` FROM `System_AdminMenu` WHERE `Code` = 'system-manage:menu' LIMIT 1), (@shortSnowflakeTimestamp * 1000 + (@shortSnowflakeSequence := @shortSnowflakeSequence + 1)));
|
||||
INSERT INTO `System_AdminMenu` (`Id`, `ParentId`, `Name`, `Code`, `Path`, `Component`, `Icon`, `Sort`, `IsVisible`, `Status`, `IsDeleted`, `CreatedBy`, `CreatedAt`, `UpdatedBy`, `UpdatedAt`)
|
||||
VALUES (@system_manage_menu_id, @system_manage_id, '菜单管理', 'system-manage:menu', '/system-manage/menu-manage', 'view.system-manage_menu-manage', 'mingcute:list-collapse-fill', 3, b'1', 1, b'0', @createdBy, @now, @createdBy, @now)
|
||||
ON DUPLICATE KEY UPDATE `ParentId` = VALUES(`ParentId`), `Name` = VALUES(`Name`), `Path` = VALUES(`Path`), `Component` = VALUES(`Component`), `Icon` = VALUES(`Icon`), `Sort` = VALUES(`Sort`), `IsVisible` = VALUES(`IsVisible`), `Status` = VALUES(`Status`), `IsDeleted` = b'0', `UpdatedBy` = @createdBy, `UpdatedAt` = @now;
|
||||
|
||||
SET @system_manage_operation_log_id = COALESCE((SELECT `Id` FROM `System_AdminMenu` WHERE `Code` = 'system-manage:operation-log' LIMIT 1), (@shortSnowflakeTimestamp * 1000 + (@shortSnowflakeSequence := @shortSnowflakeSequence + 1)));
|
||||
INSERT INTO `System_AdminMenu` (`Id`, `ParentId`, `Name`, `Code`, `Path`, `Component`, `Icon`, `Sort`, `IsVisible`, `Status`, `IsDeleted`, `CreatedBy`, `CreatedAt`, `UpdatedBy`, `UpdatedAt`)
|
||||
VALUES (@system_manage_operation_log_id, @system_manage_id, 'system-manage_operation-log-management', 'system-manage:operation-log', '/system-manage/operation-log-management', 'view.system-manage_operation-log-management', 'ri:file-list-3-line', 4, b'1', 1, b'0', @createdBy, @now, @createdBy, @now)
|
||||
ON DUPLICATE KEY UPDATE `ParentId` = VALUES(`ParentId`), `Name` = VALUES(`Name`), `Path` = VALUES(`Path`), `Component` = VALUES(`Component`), `Icon` = VALUES(`Icon`), `Sort` = VALUES(`Sort`), `IsVisible` = VALUES(`IsVisible`), `Status` = VALUES(`Status`), `IsDeleted` = b'0', `UpdatedBy` = @createdBy, `UpdatedAt` = @now;
|
||||
|
||||
SET @system_manage_user_id = COALESCE((SELECT `Id` FROM `System_AdminMenu` WHERE `Code` = 'system-manage:user' LIMIT 1), (@shortSnowflakeTimestamp * 1000 + (@shortSnowflakeSequence := @shortSnowflakeSequence + 1)));
|
||||
INSERT INTO `System_AdminMenu` (`Id`, `ParentId`, `Name`, `Code`, `Path`, `Component`, `Icon`, `Sort`, `IsVisible`, `Status`, `IsDeleted`, `CreatedBy`, `CreatedAt`, `UpdatedBy`, `UpdatedAt`)
|
||||
VALUES (@system_manage_user_id, @system_manage_id, 'admin管理', 'system-manage:user', '/system-manage/user-manage', 'view.system-manage_user-manage', 'ic:sharp-admin-panel-settings', 5, b'1', 1, b'0', @createdBy, @now, @createdBy, @now)
|
||||
ON DUPLICATE KEY UPDATE `ParentId` = VALUES(`ParentId`), `Name` = VALUES(`Name`), `Path` = VALUES(`Path`), `Component` = VALUES(`Component`), `Icon` = VALUES(`Icon`), `Sort` = VALUES(`Sort`), `IsVisible` = VALUES(`IsVisible`), `Status` = VALUES(`Status`), `IsDeleted` = b'0', `UpdatedBy` = @createdBy, `UpdatedAt` = @now;
|
||||
|
||||
SET @system_manage_user_management_id = COALESCE((SELECT `Id` FROM `System_AdminMenu` WHERE `Code` = 'system-manage:user-management' LIMIT 1), (@shortSnowflakeTimestamp * 1000 + (@shortSnowflakeSequence := @shortSnowflakeSequence + 1)));
|
||||
INSERT INTO `System_AdminMenu` (`Id`, `ParentId`, `Name`, `Code`, `Path`, `Component`, `Icon`, `Sort`, `IsVisible`, `Status`, `IsDeleted`, `CreatedBy`, `CreatedAt`, `UpdatedBy`, `UpdatedAt`)
|
||||
VALUES (@system_manage_user_management_id, @system_manage_id, '用户管理', 'system-manage:user-management', '/system-manage/users-management', 'view.system-manage_users-management', 'ri:user-2-line', 6, b'1', 1, b'0', @createdBy, @now, @createdBy, @now)
|
||||
ON DUPLICATE KEY UPDATE `ParentId` = VALUES(`ParentId`), `Name` = VALUES(`Name`), `Path` = VALUES(`Path`), `Component` = VALUES(`Component`), `Icon` = VALUES(`Icon`), `Sort` = VALUES(`Sort`), `IsVisible` = VALUES(`IsVisible`), `Status` = VALUES(`Status`), `IsDeleted` = b'0', `UpdatedBy` = @createdBy, `UpdatedAt` = @now;
|
||||
|
||||
SET @help_center_id = COALESCE((SELECT `Id` FROM `System_AdminMenu` WHERE `Code` = 'help-center' LIMIT 1), (@shortSnowflakeTimestamp * 1000 + (@shortSnowflakeSequence := @shortSnowflakeSequence + 1)));
|
||||
INSERT INTO `System_AdminMenu` (`Id`, `ParentId`, `Name`, `Code`, `Path`, `Component`, `Icon`, `Sort`, `IsVisible`, `Status`, `IsDeleted`, `CreatedBy`, `CreatedAt`, `UpdatedBy`, `UpdatedAt`)
|
||||
VALUES (@help_center_id, 0, '帮助中心', 'help-center', '/help-center', 'layout.base', 'ic:baseline-support-agent', 4, b'1', 1, b'0', @createdBy, @now, @createdBy, @now)
|
||||
ON DUPLICATE KEY UPDATE `ParentId` = VALUES(`ParentId`), `Name` = VALUES(`Name`), `Path` = VALUES(`Path`), `Component` = VALUES(`Component`), `Icon` = VALUES(`Icon`), `Sort` = VALUES(`Sort`), `IsVisible` = VALUES(`IsVisible`), `Status` = VALUES(`Status`), `IsDeleted` = b'0', `UpdatedBy` = @createdBy, `UpdatedAt` = @now;
|
||||
|
||||
SET @help_center_contact_support_id = COALESCE((SELECT `Id` FROM `System_AdminMenu` WHERE `Code` = 'help-center:contact-support' LIMIT 1), (@shortSnowflakeTimestamp * 1000 + (@shortSnowflakeSequence := @shortSnowflakeSequence + 1)));
|
||||
INSERT INTO `System_AdminMenu` (`Id`, `ParentId`, `Name`, `Code`, `Path`, `Component`, `Icon`, `Sort`, `IsVisible`, `Status`, `IsDeleted`, `CreatedBy`, `CreatedAt`, `UpdatedBy`, `UpdatedAt`)
|
||||
VALUES (@help_center_contact_support_id, @help_center_id, '联系客服', 'help-center:contact-support', '/help-center/contact-support', 'view.help-center_contact-support', 'ri:customer-service-2-line', 0, b'1', 1, b'0', @createdBy, @now, @createdBy, @now)
|
||||
ON DUPLICATE KEY UPDATE `ParentId` = VALUES(`ParentId`), `Name` = VALUES(`Name`), `Path` = VALUES(`Path`), `Component` = VALUES(`Component`), `Icon` = VALUES(`Icon`), `Sort` = VALUES(`Sort`), `IsVisible` = VALUES(`IsVisible`), `Status` = VALUES(`Status`), `IsDeleted` = b'0', `UpdatedBy` = @createdBy, `UpdatedAt` = @now;
|
||||
|
||||
SET @help_center_usage_help_id = COALESCE((SELECT `Id` FROM `System_AdminMenu` WHERE `Code` = 'help-center:usage-help' LIMIT 1), (@shortSnowflakeTimestamp * 1000 + (@shortSnowflakeSequence := @shortSnowflakeSequence + 1)));
|
||||
INSERT INTO `System_AdminMenu` (`Id`, `ParentId`, `Name`, `Code`, `Path`, `Component`, `Icon`, `Sort`, `IsVisible`, `Status`, `IsDeleted`, `CreatedBy`, `CreatedAt`, `UpdatedBy`, `UpdatedAt`)
|
||||
VALUES (@help_center_usage_help_id, @help_center_id, '使用帮助', 'help-center:usage-help', '/help-center/usage-help', 'view.help-center_usage-help', 'ic:baseline-support-agent', 1, b'1', 1, b'0', @createdBy, @now, @createdBy, @now)
|
||||
ON DUPLICATE KEY UPDATE `ParentId` = VALUES(`ParentId`), `Name` = VALUES(`Name`), `Path` = VALUES(`Path`), `Component` = VALUES(`Component`), `Icon` = VALUES(`Icon`), `Sort` = VALUES(`Sort`), `IsVisible` = VALUES(`IsVisible`), `Status` = VALUES(`Status`), `IsDeleted` = b'0', `UpdatedBy` = @createdBy, `UpdatedAt` = @now;
|
||||
|
||||
SET @pen_management_id = COALESCE((SELECT `Id` FROM `System_AdminMenu` WHERE `Code` = 'pen-management' LIMIT 1), (@shortSnowflakeTimestamp * 1000 + (@shortSnowflakeSequence := @shortSnowflakeSequence + 1)));
|
||||
INSERT INTO `System_AdminMenu` (`Id`, `ParentId`, `Name`, `Code`, `Path`, `Component`, `Icon`, `Sort`, `IsVisible`, `Status`, `IsDeleted`, `CreatedBy`, `CreatedAt`, `UpdatedBy`, `UpdatedAt`)
|
||||
VALUES (@pen_management_id, 0, '点阵笔管理', 'pen-management', '/pen-management', 'layout.base', 'ic:round-edit-off', 8, b'1', 1, b'0', @createdBy, @now, @createdBy, @now)
|
||||
ON DUPLICATE KEY UPDATE `ParentId` = VALUES(`ParentId`), `Name` = VALUES(`Name`), `Path` = VALUES(`Path`), `Component` = VALUES(`Component`), `Icon` = VALUES(`Icon`), `Sort` = VALUES(`Sort`), `IsVisible` = VALUES(`IsVisible`), `Status` = VALUES(`Status`), `IsDeleted` = b'0', `UpdatedBy` = @createdBy, `UpdatedAt` = @now;
|
||||
|
||||
SET @pen_management_pen_list_id = COALESCE((SELECT `Id` FROM `System_AdminMenu` WHERE `Code` = 'pen-management:list' LIMIT 1), (@shortSnowflakeTimestamp * 1000 + (@shortSnowflakeSequence := @shortSnowflakeSequence + 1)));
|
||||
INSERT INTO `System_AdminMenu` (`Id`, `ParentId`, `Name`, `Code`, `Path`, `Component`, `Icon`, `Sort`, `IsVisible`, `Status`, `IsDeleted`, `CreatedBy`, `CreatedAt`, `UpdatedBy`, `UpdatedAt`)
|
||||
VALUES (@pen_management_pen_list_id, @pen_management_id, '点阵笔列表', 'pen-management:list', '/pen-management/pen-list', 'view.pen-management_pen-list', 'ri:bar-chart-2-line', 0, b'1', 1, b'0', @createdBy, @now, @createdBy, @now)
|
||||
ON DUPLICATE KEY UPDATE `ParentId` = VALUES(`ParentId`), `Name` = VALUES(`Name`), `Path` = VALUES(`Path`), `Component` = VALUES(`Component`), `Icon` = VALUES(`Icon`), `Sort` = VALUES(`Sort`), `IsVisible` = VALUES(`IsVisible`), `Status` = VALUES(`Status`), `IsDeleted` = b'0', `UpdatedBy` = @createdBy, `UpdatedAt` = @now;
|
||||
|
||||
SET @pen_management_coordinate_data_id = COALESCE((SELECT `Id` FROM `System_AdminMenu` WHERE `Code` = 'pen-management:coordinate-data' LIMIT 1), (@shortSnowflakeTimestamp * 1000 + (@shortSnowflakeSequence := @shortSnowflakeSequence + 1)));
|
||||
INSERT INTO `System_AdminMenu` (`Id`, `ParentId`, `Name`, `Code`, `Path`, `Component`, `Icon`, `Sort`, `IsVisible`, `Status`, `IsDeleted`, `CreatedBy`, `CreatedAt`, `UpdatedBy`, `UpdatedAt`)
|
||||
VALUES (@pen_management_coordinate_data_id, @pen_management_id, '点阵笔坐标数据', 'pen-management:coordinate-data', '/pen-management/coordinate-data', 'view.pen-management_coordinate-data', 'ic:baseline-edit', 1, b'1', 1, b'0', @createdBy, @now, @createdBy, @now)
|
||||
ON DUPLICATE KEY UPDATE `ParentId` = VALUES(`ParentId`), `Name` = VALUES(`Name`), `Path` = VALUES(`Path`), `Component` = VALUES(`Component`), `Icon` = VALUES(`Icon`), `Sort` = VALUES(`Sort`), `IsVisible` = VALUES(`IsVisible`), `Status` = VALUES(`Status`), `IsDeleted` = b'0', `UpdatedBy` = @createdBy, `UpdatedAt` = @now;
|
||||
22
DatabaseScripts/message_outbox.sql
Normal file
22
DatabaseScripts/message_outbox.sql
Normal file
@ -0,0 +1,22 @@
|
||||
CREATE TABLE IF NOT EXISTS `Message_Outbox` (
|
||||
`Id` bigint NOT NULL,
|
||||
`Exchange` varchar(200) NOT NULL,
|
||||
`Queue` varchar(200) NOT NULL,
|
||||
`RoutingKey` varchar(200) NOT NULL,
|
||||
`Payload` json NOT NULL,
|
||||
`RetryCount` int NOT NULL DEFAULT 0,
|
||||
`NextRetryAt` datetime NULL,
|
||||
`SentAt` datetime NULL,
|
||||
`LastError` varchar(2000) NULL,
|
||||
`BusinessType` varchar(100) NOT NULL,
|
||||
`BusinessId` bigint NOT NULL,
|
||||
`Status` int NOT NULL DEFAULT 0,
|
||||
`IsDeleted` bit NOT NULL DEFAULT b'0',
|
||||
`CreatedBy` varchar(100) NOT NULL,
|
||||
`CreatedAt` datetime NOT NULL,
|
||||
`UpdatedBy` varchar(100) NULL,
|
||||
`UpdatedAt` datetime NULL,
|
||||
PRIMARY KEY (`Id`),
|
||||
KEY `idx_message_outbox_status_next_retry` (`Status`, `NextRetryAt`, `CreatedAt`),
|
||||
KEY `idx_message_outbox_business` (`BusinessType`, `BusinessId`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
5
DatabaseScripts/stability_unique_constraints.sql
Normal file
5
DatabaseScripts/stability_unique_constraints.sql
Normal file
@ -0,0 +1,5 @@
|
||||
ALTER TABLE `CheckIn_Record`
|
||||
ADD UNIQUE KEY `uk_checkin_record_user_date_type_deleted` (`UserId`, `CheckInDate`, `Type`, `IsDeleted`);
|
||||
|
||||
ALTER TABLE `User_Bag`
|
||||
ADD UNIQUE KEY `uk_user_bag_available_item` (`UserId`, `ItemId`, `Status`, `IsDeleted`);
|
||||
5
Directory.Build.props
Normal file
5
Directory.Build.props
Normal file
@ -0,0 +1,5 @@
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<NoWarn>$(NoWarn);CS0105;CS0108;CS0168;CS0169;CS0618;CS1570;CS1572;CS1573;CS1591;CS8600;CS8601;CS8602;CS8603;CS8604;CS8618;CS8625;CS8629;CS8634;CS8714;CS9113</NoWarn>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
473
QYZH.InteractiveMagazine.Common/Helpers/QrCodeHelper.cs
Normal file
473
QYZH.InteractiveMagazine.Common/Helpers/QrCodeHelper.cs
Normal file
@ -0,0 +1,473 @@
|
||||
using System.IO.Compression;
|
||||
using System.Text;
|
||||
|
||||
namespace QYZH.InteractiveMagazine.Common.Helpers;
|
||||
|
||||
/// <summary>
|
||||
/// 二维码帮助类
|
||||
/// </summary>
|
||||
public static class QrCodeHelper
|
||||
{
|
||||
private const int Version = 4;
|
||||
private const int Size = Version * 4 + 17;
|
||||
private const int DataCodewordCount = 80;
|
||||
private const int ErrorCorrectionCodewordCount = 20;
|
||||
private const int QuietZone = 4;
|
||||
private const int Scale = 10;
|
||||
private static readonly int[] AlignmentPatternPositions = [6, 26];
|
||||
|
||||
/// <summary>
|
||||
/// 生成二维码PNG图片字节
|
||||
/// </summary>
|
||||
/// <param name="content">二维码内容</param>
|
||||
/// <returns>PNG图片字节</returns>
|
||||
public static byte[] GeneratePng(string content)
|
||||
{
|
||||
var dataCodewords = CreateDataCodewords(content);
|
||||
var allCodewords = dataCodewords.Concat(CreateErrorCorrectionCodewords(dataCodewords)).ToArray();
|
||||
var modules = BuildModules(allCodewords);
|
||||
var imageSize = (Size + QuietZone * 2) * Scale;
|
||||
var pixels = new byte[imageSize * imageSize * 4];
|
||||
|
||||
for (var i = 0; i < pixels.Length; i += 4)
|
||||
{
|
||||
pixels[i] = 255;
|
||||
pixels[i + 1] = 255;
|
||||
pixels[i + 2] = 255;
|
||||
pixels[i + 3] = 255;
|
||||
}
|
||||
|
||||
for (var y = 0; y < Size; y++)
|
||||
{
|
||||
for (var x = 0; x < Size; x++)
|
||||
{
|
||||
if (modules[y, x])
|
||||
{
|
||||
FillModule(pixels, imageSize, x + QuietZone, y + QuietZone);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return EncodePng(imageSize, imageSize, pixels);
|
||||
}
|
||||
|
||||
private static byte[] CreateDataCodewords(string content)
|
||||
{
|
||||
var bytes = Encoding.UTF8.GetBytes(content);
|
||||
if (bytes.Length > 78)
|
||||
{
|
||||
throw new InvalidOperationException("二维码内容过长");
|
||||
}
|
||||
|
||||
var bits = new List<bool>();
|
||||
AppendBits(bits, 0b0100, 4);
|
||||
AppendBits(bits, bytes.Length, 8);
|
||||
foreach (var value in bytes)
|
||||
{
|
||||
AppendBits(bits, value, 8);
|
||||
}
|
||||
|
||||
var remaining = DataCodewordCount * 8 - bits.Count;
|
||||
AppendBits(bits, 0, Math.Min(4, remaining));
|
||||
|
||||
while (bits.Count % 8 != 0)
|
||||
{
|
||||
bits.Add(false);
|
||||
}
|
||||
|
||||
var data = BitsToBytes(bits);
|
||||
var pad = true;
|
||||
while (data.Count < DataCodewordCount)
|
||||
{
|
||||
data.Add((byte)(pad ? 0xEC : 0x11));
|
||||
pad = !pad;
|
||||
}
|
||||
|
||||
return data.ToArray();
|
||||
}
|
||||
|
||||
private static bool[,] BuildModules(byte[] codewords)
|
||||
{
|
||||
var modules = new bool[Size, Size];
|
||||
var reserved = new bool[Size, Size];
|
||||
|
||||
AddFinder(modules, reserved, 0, 0);
|
||||
AddFinder(modules, reserved, Size - 7, 0);
|
||||
AddFinder(modules, reserved, 0, Size - 7);
|
||||
AddTimingPatterns(modules, reserved);
|
||||
AddAlignmentPatterns(modules, reserved);
|
||||
ReserveFormatAreas(reserved);
|
||||
|
||||
modules[Version * 4 + 9, 8] = true;
|
||||
reserved[Version * 4 + 9, 8] = true;
|
||||
|
||||
AddDataModules(modules, reserved, codewords);
|
||||
AddFormatBits(modules);
|
||||
|
||||
return modules;
|
||||
}
|
||||
|
||||
private static void AddFinder(bool[,] modules, bool[,] reserved, int left, int top)
|
||||
{
|
||||
for (var y = -1; y <= 7; y++)
|
||||
{
|
||||
for (var x = -1; x <= 7; x++)
|
||||
{
|
||||
var px = left + x;
|
||||
var py = top + y;
|
||||
if (px < 0 || py < 0 || px >= Size || py >= Size)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
reserved[py, px] = true;
|
||||
modules[py, px] = x >= 0 && x <= 6 && y >= 0 && y <= 6
|
||||
&& (x == 0 || x == 6 || y == 0 || y == 6 || (x >= 2 && x <= 4 && y >= 2 && y <= 4));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void AddTimingPatterns(bool[,] modules, bool[,] reserved)
|
||||
{
|
||||
for (var i = 8; i < Size - 8; i++)
|
||||
{
|
||||
var isDark = i % 2 == 0;
|
||||
modules[6, i] = isDark;
|
||||
modules[i, 6] = isDark;
|
||||
reserved[6, i] = true;
|
||||
reserved[i, 6] = true;
|
||||
}
|
||||
}
|
||||
|
||||
private static void AddAlignmentPatterns(bool[,] modules, bool[,] reserved)
|
||||
{
|
||||
foreach (var centerY in AlignmentPatternPositions)
|
||||
{
|
||||
foreach (var centerX in AlignmentPatternPositions)
|
||||
{
|
||||
if (reserved[centerY, centerX])
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
for (var y = -2; y <= 2; y++)
|
||||
{
|
||||
for (var x = -2; x <= 2; x++)
|
||||
{
|
||||
var px = centerX + x;
|
||||
var py = centerY + y;
|
||||
reserved[py, px] = true;
|
||||
modules[py, px] = Math.Max(Math.Abs(x), Math.Abs(y)) != 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void ReserveFormatAreas(bool[,] reserved)
|
||||
{
|
||||
for (var i = 0; i <= 8; i++)
|
||||
{
|
||||
reserved[8, i] = true;
|
||||
reserved[i, 8] = true;
|
||||
}
|
||||
|
||||
for (var i = 0; i < 8; i++)
|
||||
{
|
||||
reserved[8, Size - 1 - i] = true;
|
||||
}
|
||||
|
||||
for (var i = 0; i < 7; i++)
|
||||
{
|
||||
reserved[Size - 1 - i, 8] = true;
|
||||
}
|
||||
}
|
||||
|
||||
private static void AddDataModules(bool[,] modules, bool[,] reserved, byte[] codewords)
|
||||
{
|
||||
var bits = new List<bool>();
|
||||
foreach (var codeword in codewords)
|
||||
{
|
||||
AppendBits(bits, codeword, 8);
|
||||
}
|
||||
|
||||
var bitIndex = 0;
|
||||
var upward = true;
|
||||
for (var right = Size - 1; right >= 1; right -= 2)
|
||||
{
|
||||
if (right == 6)
|
||||
{
|
||||
right--;
|
||||
}
|
||||
|
||||
for (var i = 0; i < Size; i++)
|
||||
{
|
||||
var y = upward ? Size - 1 - i : i;
|
||||
for (var j = 0; j < 2; j++)
|
||||
{
|
||||
var x = right - j;
|
||||
if (reserved[y, x])
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var bit = bitIndex < bits.Count && bits[bitIndex++];
|
||||
if ((x + y) % 2 == 0)
|
||||
{
|
||||
bit = !bit;
|
||||
}
|
||||
|
||||
modules[y, x] = bit;
|
||||
}
|
||||
}
|
||||
|
||||
upward = !upward;
|
||||
}
|
||||
}
|
||||
|
||||
private static void AddFormatBits(bool[,] modules)
|
||||
{
|
||||
var format = GetFormatBits();
|
||||
|
||||
for (var i = 0; i <= 5; i++)
|
||||
{
|
||||
modules[i, 8] = GetBit(format, i);
|
||||
}
|
||||
|
||||
modules[7, 8] = GetBit(format, 6);
|
||||
modules[8, 8] = GetBit(format, 7);
|
||||
modules[8, 7] = GetBit(format, 8);
|
||||
|
||||
for (var i = 9; i < 15; i++)
|
||||
{
|
||||
modules[8, 14 - i] = GetBit(format, i);
|
||||
}
|
||||
|
||||
for (var i = 0; i < 8; i++)
|
||||
{
|
||||
modules[8, Size - 1 - i] = GetBit(format, i);
|
||||
}
|
||||
|
||||
for (var i = 8; i < 15; i++)
|
||||
{
|
||||
modules[Size - 15 + i, 8] = GetBit(format, i);
|
||||
}
|
||||
|
||||
modules[Size - 8, 8] = true;
|
||||
}
|
||||
|
||||
private static int GetFormatBits()
|
||||
{
|
||||
const int errorCorrectionLevelBits = 1;
|
||||
const int mask = 0;
|
||||
var data = (errorCorrectionLevelBits << 3) | mask;
|
||||
var value = data << 10;
|
||||
const int generator = 0x537;
|
||||
|
||||
for (var i = 14; i >= 10; i--)
|
||||
{
|
||||
if (((value >> i) & 1) != 0)
|
||||
{
|
||||
value ^= generator << (i - 10);
|
||||
}
|
||||
}
|
||||
|
||||
return ((data << 10) | (value & 0x3FF)) ^ 0x5412;
|
||||
}
|
||||
|
||||
private static byte[] CreateErrorCorrectionCodewords(byte[] data)
|
||||
{
|
||||
var generator = CreateGeneratorPolynomial(ErrorCorrectionCodewordCount);
|
||||
var result = new byte[ErrorCorrectionCodewordCount];
|
||||
|
||||
foreach (var b in data)
|
||||
{
|
||||
var factor = b ^ result[0];
|
||||
Array.Copy(result, 1, result, 0, result.Length - 1);
|
||||
result[^1] = 0;
|
||||
|
||||
for (var i = 0; i < result.Length; i++)
|
||||
{
|
||||
result[i] ^= GaloisMultiply(generator[i + 1], factor);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static byte[] CreateGeneratorPolynomial(int degree)
|
||||
{
|
||||
var result = new List<byte> { 1 };
|
||||
for (var i = 0; i < degree; i++)
|
||||
{
|
||||
var next = new byte[result.Count + 1];
|
||||
for (var j = 0; j < result.Count; j++)
|
||||
{
|
||||
next[j] ^= result[j];
|
||||
next[j + 1] ^= GaloisMultiply(result[j], GaloisPower(i));
|
||||
}
|
||||
|
||||
result = next.ToList();
|
||||
}
|
||||
|
||||
return result.ToArray();
|
||||
}
|
||||
|
||||
private static byte GaloisPower(int exponent)
|
||||
{
|
||||
var value = 1;
|
||||
for (var i = 0; i < exponent; i++)
|
||||
{
|
||||
value <<= 1;
|
||||
if ((value & 0x100) != 0)
|
||||
{
|
||||
value ^= 0x11D;
|
||||
}
|
||||
}
|
||||
|
||||
return (byte)value;
|
||||
}
|
||||
|
||||
private static byte GaloisMultiply(int x, int y)
|
||||
{
|
||||
var result = 0;
|
||||
while (y != 0)
|
||||
{
|
||||
if ((y & 1) != 0)
|
||||
{
|
||||
result ^= x;
|
||||
}
|
||||
|
||||
x <<= 1;
|
||||
if ((x & 0x100) != 0)
|
||||
{
|
||||
x ^= 0x11D;
|
||||
}
|
||||
|
||||
y >>= 1;
|
||||
}
|
||||
|
||||
return (byte)result;
|
||||
}
|
||||
|
||||
private static void AppendBits(List<bool> bits, int value, int count)
|
||||
{
|
||||
for (var i = count - 1; i >= 0; i--)
|
||||
{
|
||||
bits.Add(((value >> i) & 1) != 0);
|
||||
}
|
||||
}
|
||||
|
||||
private static List<byte> BitsToBytes(List<bool> bits)
|
||||
{
|
||||
var result = new List<byte>();
|
||||
for (var i = 0; i < bits.Count; i += 8)
|
||||
{
|
||||
var value = 0;
|
||||
for (var j = 0; j < 8; j++)
|
||||
{
|
||||
value = (value << 1) | (bits[i + j] ? 1 : 0);
|
||||
}
|
||||
|
||||
result.Add((byte)value);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static bool GetBit(int value, int index)
|
||||
{
|
||||
return ((value >> index) & 1) != 0;
|
||||
}
|
||||
|
||||
private static void FillModule(byte[] pixels, int imageSize, int moduleX, int moduleY)
|
||||
{
|
||||
var startX = moduleX * Scale;
|
||||
var startY = moduleY * Scale;
|
||||
|
||||
for (var y = 0; y < Scale; y++)
|
||||
{
|
||||
for (var x = 0; x < Scale; x++)
|
||||
{
|
||||
var index = ((startY + y) * imageSize + startX + x) * 4;
|
||||
pixels[index] = 0;
|
||||
pixels[index + 1] = 0;
|
||||
pixels[index + 2] = 0;
|
||||
pixels[index + 3] = 255;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static byte[] EncodePng(int width, int height, byte[] rgba)
|
||||
{
|
||||
using var output = new MemoryStream();
|
||||
WriteUInt(output, 0x89504E47);
|
||||
WriteUInt(output, 0x0D0A1A0A);
|
||||
|
||||
using (var ihdr = new MemoryStream())
|
||||
{
|
||||
WriteUInt(ihdr, (uint)width);
|
||||
WriteUInt(ihdr, (uint)height);
|
||||
ihdr.WriteByte(8);
|
||||
ihdr.WriteByte(6);
|
||||
ihdr.WriteByte(0);
|
||||
ihdr.WriteByte(0);
|
||||
ihdr.WriteByte(0);
|
||||
WriteChunk(output, "IHDR", ihdr.ToArray());
|
||||
}
|
||||
|
||||
var stride = width * 4;
|
||||
using (var raw = new MemoryStream())
|
||||
{
|
||||
for (var y = 0; y < height; y++)
|
||||
{
|
||||
raw.WriteByte(0);
|
||||
raw.Write(rgba, y * stride, stride);
|
||||
}
|
||||
|
||||
using var compressed = new MemoryStream();
|
||||
using (var zlib = new ZLibStream(compressed, CompressionLevel.SmallestSize, true))
|
||||
{
|
||||
raw.Position = 0;
|
||||
raw.CopyTo(zlib);
|
||||
}
|
||||
|
||||
WriteChunk(output, "IDAT", compressed.ToArray());
|
||||
}
|
||||
|
||||
WriteChunk(output, "IEND", []);
|
||||
return output.ToArray();
|
||||
}
|
||||
|
||||
private static void WriteChunk(Stream stream, string type, byte[] data)
|
||||
{
|
||||
WriteUInt(stream, (uint)data.Length);
|
||||
var typeBytes = Encoding.ASCII.GetBytes(type);
|
||||
stream.Write(typeBytes);
|
||||
stream.Write(data);
|
||||
WriteUInt(stream, Crc32(typeBytes.Concat(data).ToArray()));
|
||||
}
|
||||
|
||||
private static void WriteUInt(Stream stream, uint value)
|
||||
{
|
||||
stream.WriteByte((byte)(value >> 24));
|
||||
stream.WriteByte((byte)(value >> 16));
|
||||
stream.WriteByte((byte)(value >> 8));
|
||||
stream.WriteByte((byte)value);
|
||||
}
|
||||
|
||||
private static uint Crc32(byte[] bytes)
|
||||
{
|
||||
var crc = 0xFFFFFFFFu;
|
||||
foreach (var b in bytes)
|
||||
{
|
||||
crc ^= b;
|
||||
for (var i = 0; i < 8; i++)
|
||||
{
|
||||
crc = (crc & 1) == 1 ? (crc >> 1) ^ 0xEDB88320u : crc >> 1;
|
||||
}
|
||||
}
|
||||
|
||||
return ~crc;
|
||||
}
|
||||
}
|
||||
37
QYZH.InteractiveMagazine.Common/Helpers/RandomIdHelper.cs
Normal file
37
QYZH.InteractiveMagazine.Common/Helpers/RandomIdHelper.cs
Normal file
@ -0,0 +1,37 @@
|
||||
using System.Security.Cryptography;
|
||||
|
||||
namespace QYZH.InteractiveMagazine.Common.Helpers;
|
||||
|
||||
/// <summary>
|
||||
/// 随机ID帮助类
|
||||
/// </summary>
|
||||
public static class RandomIdHelper
|
||||
{
|
||||
private const long DefaultMinValue = 1_000_000_000_000L;
|
||||
private const long DefaultMaxValue = 9_000_000_000_000_000L;
|
||||
|
||||
/// <summary>
|
||||
/// 生成不可预测的正数长整型ID
|
||||
/// </summary>
|
||||
public static long GenerateLongId(long minValue = DefaultMinValue, long maxValue = DefaultMaxValue)
|
||||
{
|
||||
if (minValue <= 0 || minValue >= maxValue)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(minValue), "随机ID范围配置错误");
|
||||
}
|
||||
|
||||
var range = (ulong)(maxValue - minValue);
|
||||
var limit = ulong.MaxValue - (ulong.MaxValue % range);
|
||||
|
||||
Span<byte> bytes = stackalloc byte[sizeof(ulong)];
|
||||
while (true)
|
||||
{
|
||||
RandomNumberGenerator.Fill(bytes);
|
||||
var value = BitConverter.ToUInt64(bytes);
|
||||
if (value < limit)
|
||||
{
|
||||
return minValue + (long)(value % range);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
80
QYZH.InteractiveMagazine.IService/IAdminPermissionService.cs
Normal file
80
QYZH.InteractiveMagazine.IService/IAdminPermissionService.cs
Normal file
@ -0,0 +1,80 @@
|
||||
using QYZH.InteractiveMagazine.Models.Dto;
|
||||
using QYZH.InteractiveMagazine.Models.Entity;
|
||||
|
||||
namespace QYZH.InteractiveMagazine.IService;
|
||||
|
||||
/// <summary>
|
||||
/// 后台权限管理服务接口
|
||||
/// </summary>
|
||||
public interface IAdminPermissionService : IBaseService<AdminRole>
|
||||
{
|
||||
/// <summary>
|
||||
/// 创建菜单
|
||||
/// </summary>
|
||||
Task<AdminMenuOutput> CreateMenuAsync(AdminMenuInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 更新菜单
|
||||
/// </summary>
|
||||
Task<AdminMenuOutput> UpdateMenuAsync(long id, AdminMenuInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 删除菜单
|
||||
/// </summary>
|
||||
Task DeleteMenuAsync(long id);
|
||||
|
||||
/// <summary>
|
||||
/// 获取菜单树
|
||||
/// </summary>
|
||||
Task<List<AdminMenuOutput>> GetMenuTreeAsync(AdminMenuQueryInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 创建角色
|
||||
/// </summary>
|
||||
Task<AdminRoleOutput> CreateRoleAsync(AdminRoleInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 更新角色
|
||||
/// </summary>
|
||||
Task<AdminRoleOutput> UpdateRoleAsync(long id, AdminRoleInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 删除角色
|
||||
/// </summary>
|
||||
Task DeleteRoleAsync(long id);
|
||||
|
||||
/// <summary>
|
||||
/// 获取角色详情
|
||||
/// </summary>
|
||||
Task<AdminRoleOutput> GetRoleByIdAsync(long id);
|
||||
|
||||
/// <summary>
|
||||
/// 获取角色分页列表
|
||||
/// </summary>
|
||||
Task<PageListModel<AdminRoleOutput>> GetRoleListAsync(AdminRoleQueryInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 分配角色菜单
|
||||
/// </summary>
|
||||
Task AssignRoleMenusAsync(long roleId, AssignRoleMenusInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 分配管理员角色
|
||||
/// </summary>
|
||||
Task AssignAdminUserRolesAsync(long adminUserId, AssignAdminUserRolesInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 获取管理员菜单树
|
||||
/// </summary>
|
||||
Task<List<AdminMenuOutput>> GetAdminUserMenuTreeAsync(long adminUserId);
|
||||
|
||||
/// <summary>
|
||||
/// 获取管理员角色
|
||||
/// </summary>
|
||||
Task<List<AdminRoleSimpleOutput>> GetAdminUserRolesAsync(long adminUserId);
|
||||
|
||||
/// <summary>
|
||||
/// 获取管理员权限编码
|
||||
/// </summary>
|
||||
Task<List<string>> GetAdminUserPermissionCodesAsync(long adminUserId);
|
||||
}
|
||||
57
QYZH.InteractiveMagazine.IService/IAiBasePromptService.cs
Normal file
57
QYZH.InteractiveMagazine.IService/IAiBasePromptService.cs
Normal file
@ -0,0 +1,57 @@
|
||||
using QYZH.InteractiveMagazine.Models.Dto;
|
||||
using QYZH.InteractiveMagazine.Models.Entity;
|
||||
|
||||
namespace QYZH.InteractiveMagazine.IService;
|
||||
|
||||
/// <summary>
|
||||
/// AIPrompt配置服务接口
|
||||
/// </summary>
|
||||
public interface IAiBasePromptService : IBaseService<AiBasePrompt>
|
||||
{
|
||||
/// <summary>
|
||||
/// 创建Prompt配置
|
||||
/// </summary>
|
||||
/// <param name="input">Prompt配置输入</param>
|
||||
/// <returns>创建的Prompt配置信息</returns>
|
||||
Task<AiBasePromptOutput> CreateAsync(AiBasePromptInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 更新Prompt配置
|
||||
/// </summary>
|
||||
/// <param name="id">Prompt配置ID</param>
|
||||
/// <param name="input">Prompt配置输入</param>
|
||||
/// <returns>更新后的Prompt配置信息</returns>
|
||||
Task<AiBasePromptOutput> UpdateAsync(long id, AiBasePromptInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 删除Prompt配置(软删除)
|
||||
/// </summary>
|
||||
/// <param name="id">Prompt配置ID</param>
|
||||
Task DeleteAsync(long id);
|
||||
|
||||
/// <summary>
|
||||
/// 根据ID获取Prompt配置
|
||||
/// </summary>
|
||||
/// <param name="id">Prompt配置ID</param>
|
||||
/// <returns>Prompt配置信息</returns>
|
||||
Task<AiBasePromptOutput> GetByIdAsync(long id);
|
||||
|
||||
/// <summary>
|
||||
/// 分页查询Prompt配置列表
|
||||
/// </summary>
|
||||
/// <param name="input">查询条件</param>
|
||||
/// <returns>分页结果</returns>
|
||||
Task<PageListModel<AiBasePromptOutput>> GetListAsync(AiBasePromptQueryInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 获取全部Prompt配置(无分页)
|
||||
/// </summary>
|
||||
/// <returns>全部Prompt配置列表</returns>
|
||||
Task<List<AiBasePromptOutput>> GetAllAsync();
|
||||
|
||||
/// <summary>
|
||||
/// 切换Prompt启用/禁用状态
|
||||
/// </summary>
|
||||
/// <param name="id">Prompt配置ID</param>
|
||||
Task ToggleStatusAsync(long id);
|
||||
}
|
||||
17
QYZH.InteractiveMagazine.IService/IAiChatService.cs
Normal file
17
QYZH.InteractiveMagazine.IService/IAiChatService.cs
Normal file
@ -0,0 +1,17 @@
|
||||
using QYZH.InteractiveMagazine.Models.Dto;
|
||||
|
||||
namespace QYZH.InteractiveMagazine.IService;
|
||||
|
||||
/// <summary>
|
||||
/// AI聊天服务接口
|
||||
/// </summary>
|
||||
public interface IAiChatService
|
||||
{
|
||||
/// <summary>
|
||||
/// 流式AI对话,逐块返回AI生成的内容
|
||||
/// </summary>
|
||||
/// <param name="input">聊天输入</param>
|
||||
/// <param name="cancellationToken">取消令牌</param>
|
||||
/// <returns>AI生成的内容片段流</returns>
|
||||
IAsyncEnumerable<string> ChatAsync(AiChatInput input, CancellationToken cancellationToken = default);
|
||||
}
|
||||
61
QYZH.InteractiveMagazine.IService/IBannerService.cs
Normal file
61
QYZH.InteractiveMagazine.IService/IBannerService.cs
Normal file
@ -0,0 +1,61 @@
|
||||
using QYZH.InteractiveMagazine.Models.Dto;
|
||||
using QYZH.InteractiveMagazine.Models.Dto.Banner;
|
||||
using QYZH.InteractiveMagazine.Models.Entity;
|
||||
using QYZH.InteractiveMagazine.Models.Enum;
|
||||
|
||||
namespace QYZH.InteractiveMagazine.IService;
|
||||
|
||||
/// <summary>
|
||||
/// 轮播图服务接口
|
||||
/// </summary>
|
||||
public interface IBannerService : IBaseService<Banner>
|
||||
{
|
||||
/// <summary>
|
||||
/// 创建轮播图
|
||||
/// </summary>
|
||||
/// <param name="input">轮播图信息</param>
|
||||
/// <returns>创建后的轮播图信息</returns>
|
||||
Task<BannerOutput> CreateAsync(BannerInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 更新轮播图
|
||||
/// </summary>
|
||||
/// <param name="id">轮播图ID</param>
|
||||
/// <param name="input">轮播图信息</param>
|
||||
/// <returns>更新后的轮播图信息</returns>
|
||||
Task<BannerOutput> UpdateAsync(long id, BannerInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 删除轮播图
|
||||
/// </summary>
|
||||
/// <param name="id">轮播图ID</param>
|
||||
Task DeleteAsync(long id);
|
||||
|
||||
/// <summary>
|
||||
/// 根据ID获取轮播图
|
||||
/// </summary>
|
||||
/// <param name="id">轮播图ID</param>
|
||||
/// <returns>轮播图信息</returns>
|
||||
Task<BannerOutput> GetByIdAsync(long id);
|
||||
|
||||
/// <summary>
|
||||
/// 分页查询轮播图列表
|
||||
/// </summary>
|
||||
/// <param name="input">查询条件</param>
|
||||
/// <returns>分页结果</returns>
|
||||
Task<PageListModel<BannerOutput>> GetListAsync(BannerQueryInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 更新轮播图启用/禁用状态
|
||||
/// </summary>
|
||||
/// <param name="id">轮播图ID</param>
|
||||
/// <returns>操作结果</returns>
|
||||
Task<bool> UpdateStatusAsync(long id);
|
||||
|
||||
/// <summary>
|
||||
/// 获取启用轮播图列表
|
||||
/// </summary>
|
||||
/// <param name="position">展示位置</param>
|
||||
/// <returns>轮播图列表</returns>
|
||||
Task<List<BannerOutput>> GetEnabledListAsync(BannerPositionEnum position);
|
||||
}
|
||||
@ -18,15 +18,13 @@ public interface IJournalPageService: IBaseService<JournalPage>
|
||||
/// <returns></returns>
|
||||
Task<bool> UpdatePageNoAsync(long JournalId);
|
||||
|
||||
/// <summary>
|
||||
/// 打印书页(全部)
|
||||
/// </summary>
|
||||
/// <param name="JournalId"></param>
|
||||
/// <returns></returns>
|
||||
Task<bool> PrintJournalPageAsync(long JournalId);
|
||||
|
||||
Task<JournalPageV2Output> DetailAsync(long id);
|
||||
Task<bool> DeleteAsync(long id);
|
||||
|
||||
//Task<List<JournalPageNoArticleOutput>> PageNoArticleAsync(long id);
|
||||
/// <summary>
|
||||
/// 回调接口-自动铺码, 书页铺码后回调接口,更新书页的点阵码
|
||||
/// </summary>
|
||||
/// <param name="bookPagePrintDto"></param>
|
||||
/// <returns></returns>
|
||||
Task<bool> CallbackPageUpdatePageNo(JournalPagePrintDto journalPagePrintDto);
|
||||
}
|
||||
|
||||
@ -9,9 +9,12 @@ public interface IJournalPageTaskService : IBaseService<JournalPageTask>
|
||||
{
|
||||
Task<long?> InsertAsync(JournalPageTaskAddInput input);
|
||||
Task<bool> UpdateAsync(JournalPageTaskUpdateInput input);
|
||||
Task<bool> KeywordAnalysisAsync(JournalPageTaskKeywordAnalysis input);
|
||||
Task<JournalPageTaskOutput> DetailAsync(long id);
|
||||
|
||||
Task<bool> DeleteAsync(long id);
|
||||
Task<bool> ComplementAsync(JournalPageTaskComplementInput input);
|
||||
|
||||
Task<long> AddAnswerAsync(JournalTaskAnswerAddInput input);
|
||||
Task<bool> UpdateAnswerAsync(JournalTaskAnswerUpdateInput input);
|
||||
Task<bool> DeleteAnswerAsync(long id);
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
|
||||
|
||||
using QYZH.InteractiveMagazine.Models.Dto.Journal;
|
||||
using QYZH.InteractiveMagazine.Models.Dto.DotMatrix;
|
||||
using QYZH.InteractiveMagazine.Models.Enum;
|
||||
@ -24,6 +24,13 @@ public interface IJournalService : IBaseService<Journal>
|
||||
Task<PageListModel<JournalDto>> GetPageListAsync(PageQueryModel<JournalQueryDto> search);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 创建杂志
|
||||
/// </summary>
|
||||
/// <param name="input">杂志信息</param>
|
||||
/// <returns>新杂志ID</returns>
|
||||
Task<BaseResponse<long>> AddAsync(JournalAddDto input);
|
||||
|
||||
/// <summary>
|
||||
/// 编辑
|
||||
/// </summary>
|
||||
@ -42,6 +49,8 @@ public interface IJournalService : IBaseService<Journal>
|
||||
|
||||
Task<bool> StatusAsync(long id, JournalStatusEnum status);
|
||||
|
||||
Task<bool> PublishAsync(long id);
|
||||
|
||||
Task<DotMatrixOutput> PrintCodeAsync(long id);
|
||||
|
||||
Task<bool> ResultReportAsync(DotMatrixNoteJournalReportInput input);
|
||||
|
||||
59
QYZH.InteractiveMagazine.IService/IMaterialService.cs
Normal file
59
QYZH.InteractiveMagazine.IService/IMaterialService.cs
Normal file
@ -0,0 +1,59 @@
|
||||
using QYZH.InteractiveMagazine.Models.Dto;
|
||||
using QYZH.InteractiveMagazine.Models.Dto.Material;
|
||||
using MaterialEntity = QYZH.InteractiveMagazine.Models.Entity.Material;
|
||||
|
||||
namespace QYZH.InteractiveMagazine.IService;
|
||||
|
||||
/// <summary>
|
||||
/// 资料服务接口
|
||||
/// </summary>
|
||||
public interface IMaterialService : IBaseService<MaterialEntity>
|
||||
{
|
||||
/// <summary>
|
||||
/// 创建资料
|
||||
/// </summary>
|
||||
/// <param name="input">资料信息</param>
|
||||
/// <returns>创建后的资料信息</returns>
|
||||
Task<MaterialOutput> CreateAsync(MaterialInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 更新资料
|
||||
/// </summary>
|
||||
/// <param name="id">资料ID</param>
|
||||
/// <param name="input">资料信息</param>
|
||||
/// <returns>更新后的资料信息</returns>
|
||||
Task<MaterialOutput> UpdateAsync(long id, MaterialInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 删除资料
|
||||
/// </summary>
|
||||
/// <param name="id">资料ID</param>
|
||||
Task DeleteAsync(long id);
|
||||
|
||||
/// <summary>
|
||||
/// 根据ID获取资料
|
||||
/// </summary>
|
||||
/// <param name="id">资料ID</param>
|
||||
/// <returns>资料信息</returns>
|
||||
Task<MaterialOutput> GetByIdAsync(long id);
|
||||
|
||||
/// <summary>
|
||||
/// 分页查询资料列表
|
||||
/// </summary>
|
||||
/// <param name="input">查询条件</param>
|
||||
/// <returns>分页结果</returns>
|
||||
Task<PageListModel<MaterialOutput>> GetListAsync(MaterialQueryInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 更新资料启用/禁用状态
|
||||
/// </summary>
|
||||
/// <param name="id">资料ID</param>
|
||||
/// <returns>操作结果</returns>
|
||||
Task<bool> UpdateStatusAsync(long id);
|
||||
|
||||
/// <summary>
|
||||
/// 获取小程序可见资料列表
|
||||
/// </summary>
|
||||
/// <returns>资料列表</returns>
|
||||
Task<List<MaterialOutput>> GetEnabledListAsync();
|
||||
}
|
||||
25
QYZH.InteractiveMagazine.IService/IMessagePublishService.cs
Normal file
25
QYZH.InteractiveMagazine.IService/IMessagePublishService.cs
Normal file
@ -0,0 +1,25 @@
|
||||
using QYZH.InteractiveMagazine.Models.Dto.RabbitMQ;
|
||||
using QYZH.InteractiveMagazine.Models.Entity;
|
||||
|
||||
namespace QYZH.InteractiveMagazine.IService;
|
||||
|
||||
/// <summary>
|
||||
/// MQ消息发布服务。
|
||||
/// </summary>
|
||||
public interface IMessagePublishService : IBaseService<MessageOutbox>
|
||||
{
|
||||
/// <summary>
|
||||
/// 可靠发布消息,默认写入Outbox。
|
||||
/// </summary>
|
||||
Task<MessagePublishResult> PublishAsync<T>(MessagePublishInput<T> input, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 批量可靠发布消息,默认写入Outbox。
|
||||
/// </summary>
|
||||
Task<MessagePublishResult> PublishBatchAsync<T>(IEnumerable<MessagePublishInput<T>> inputs, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 直接发布消息,不写Outbox。
|
||||
/// </summary>
|
||||
Task<MessagePublishResult> PublishDirectAsync<T>(MessagePublishInput<T> input, CancellationToken cancellationToken = default);
|
||||
}
|
||||
@ -21,6 +21,12 @@ public interface IOperationLogService : IBaseService<OperationLog>
|
||||
/// <param name="ipAddress">IP地址(可选)</param>
|
||||
Task LogAsync(long operatorId, string operatorName, string actionType, string targetType, long targetId, string? targetName = null, string? detail = null, string? ipAddress = null);
|
||||
|
||||
/// <summary>
|
||||
/// 记录操作日志
|
||||
/// </summary>
|
||||
/// <param name="input">操作日志记录输入</param>
|
||||
Task LogAsync(OperationLogRecordInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 分页查询操作日志
|
||||
/// </summary>
|
||||
|
||||
73
QYZH.InteractiveMagazine.IService/IUserAnswerTaskService.cs
Normal file
73
QYZH.InteractiveMagazine.IService/IUserAnswerTaskService.cs
Normal file
@ -0,0 +1,73 @@
|
||||
using QYZH.InteractiveMagazine.Models.Dto.UserAnswerTaskService;
|
||||
using QYZH.InteractiveMagazine.Models.Entity;
|
||||
|
||||
namespace QYZH.InteractiveMagazine.IService;
|
||||
|
||||
/// <summary>
|
||||
/// 用户作答任务服务接口
|
||||
/// </summary>
|
||||
public interface IUserAnswerTaskService : IBaseService<JournalPageTaskUserAnswer>
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取用户期刊学习进度列表(按期刊分组)
|
||||
/// </summary>
|
||||
/// <param name="userId">用户Id</param>
|
||||
/// <returns>按期刊分组的学习进度数据</returns>
|
||||
Task<List<JournalProgressOutput>> GetJournalProgressAsync(long userId);
|
||||
|
||||
/// <summary>
|
||||
/// 获取目录列表(按一级目录分组,二级目录含完成状态)
|
||||
/// </summary>
|
||||
/// <param name="journalId">期刊Id</param>
|
||||
/// <param name="userId">用户Id</param>
|
||||
/// <returns>按一级目录分组的目录列表</returns>
|
||||
Task<List<CatalogListOutput>> GetCatalogListAsync(long journalId, long userId);
|
||||
|
||||
/// <summary>
|
||||
/// 获取一级目录概览(含二级目录详情、页码范围、任务完成状态)
|
||||
/// </summary>
|
||||
/// <param name="catalogId">一级目录Id(JournalCatalogId)</param>
|
||||
/// <param name="userId">用户Id</param>
|
||||
/// <returns>一级目录概览数据</returns>
|
||||
Task<CatalogOverviewOutput> GetCatalogOverviewAsync(long catalogId, long userId);
|
||||
|
||||
/// <summary>
|
||||
/// 获取用户答题任务列表
|
||||
/// </summary>
|
||||
/// <param name="catalogId">二级目录Id(CatalogId)</param>
|
||||
/// <param name="userId">用户Id</param>
|
||||
/// <returns>答题任务列表</returns>
|
||||
Task<UserAnswerTaskListOutput> GetUserAnswerTaskListAsync(long catalogId, long userId);
|
||||
|
||||
/// <summary>
|
||||
/// 获取跨页题目详情
|
||||
/// </summary>
|
||||
/// <param name="groupId">任务分组ID(JournalPageTaskGroupId)</param>
|
||||
/// <param name="userId">用户Id</param>
|
||||
/// <returns>跨页题目详情</returns>
|
||||
Task<CrossPageTaskOutput> GetCrossPageTaskAsync(long groupId, long userId);
|
||||
|
||||
/// <summary>
|
||||
/// 领取任务积分
|
||||
/// </summary>
|
||||
/// <param name="groupId">任务分组Id(JournalPageTask.GroupId)</param>
|
||||
/// <param name="userId">用户Id</param>
|
||||
/// <returns>领取结果</returns>
|
||||
Task<ClaimTaskPointsOutput> ClaimTaskPointsAsync(long groupId, long userId);
|
||||
|
||||
/// <summary>
|
||||
/// 批量领取任务积分(一键领取)
|
||||
/// </summary>
|
||||
/// <param name="input">批量领取参数</param>
|
||||
/// <param name="userId">用户Id</param>
|
||||
/// <returns>批量领取结果</returns>
|
||||
Task<BatchClaimPointsOutput> BatchClaimPointsAsync(BatchClaimPointsInput input, long userId);
|
||||
|
||||
/// <summary>
|
||||
/// 获取目录树形结构(一级目录和二级目录的完整树形结构,包含任务详情)
|
||||
/// </summary>
|
||||
/// <param name="journalId">期刊Id</param>
|
||||
/// <param name="userId">用户Id</param>
|
||||
/// <returns>目录树形结构数据</returns>
|
||||
Task<CatalogTreeOutput> GetCatalogTreeAsync(long journalId, long userId);
|
||||
}
|
||||
@ -30,4 +30,41 @@ public interface IUserJournalService : IBaseService<UserJournal>
|
||||
/// <param name="userId">用户Id</param>
|
||||
/// <param name="id">绑定记录Id</param>
|
||||
Task UnbindJournalAsync(long userId, long id);
|
||||
|
||||
/// <summary>
|
||||
/// 批量提交期刊二维码生成任务
|
||||
/// </summary>
|
||||
/// <param name="input">生成输入</param>
|
||||
/// <param name="operatorName">操作人名称</param>
|
||||
/// <returns>提交结果</returns>
|
||||
Task<CreateUserJournalQrCodeOutput> CreateQrCodesAsync(CreateUserJournalQrCodeInput input, string operatorName);
|
||||
|
||||
/// <summary>
|
||||
/// 分页查询期刊二维码记录
|
||||
/// </summary>
|
||||
/// <param name="input">查询条件</param>
|
||||
/// <returns>分页结果</returns>
|
||||
Task<PageListModel<UserJournalQrCodeOutput>> GetQrCodePageListAsync(UserJournalQrCodeQueryInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 获取期刊二维码详情
|
||||
/// </summary>
|
||||
/// <param name="id">二维码记录Id</param>
|
||||
/// <returns>二维码记录</returns>
|
||||
Task<UserJournalQrCodeOutput> GetQrCodeDetailAsync(long id);
|
||||
|
||||
/// <summary>
|
||||
/// 根据期刊Id获取未绑定用户的二维码列表
|
||||
/// </summary>
|
||||
/// <param name="journalId">期刊Id</param>
|
||||
/// <returns>未绑定二维码列表</returns>
|
||||
Task<List<UserJournalQrCodeOutput>> GetUnboundQrCodesByJournalIdAsync(long journalId);
|
||||
|
||||
/// <summary>
|
||||
/// 删除未绑定的期刊二维码记录
|
||||
/// </summary>
|
||||
/// <param name="input">删除输入</param>
|
||||
/// <param name="operatorName">操作人名称</param>
|
||||
/// <returns>是否成功</returns>
|
||||
Task<bool> DeleteQrCodeAsync(DeleteUserJournalQrCodeInput input, string operatorName);
|
||||
}
|
||||
|
||||
@ -11,6 +11,9 @@ namespace QYZH.InteractiveMagazine.Infrastructure.Auth;
|
||||
/// </summary>
|
||||
public static class JwtHelper
|
||||
{
|
||||
public const string AdminTokenKeyPrefix = "InteractiveMagazine:AdminAuth:Token";
|
||||
public const string WeChatTokenKeyPrefix = "InteractiveMagazine:WeChatAuth:Token";
|
||||
|
||||
/// <summary>
|
||||
/// 生成JWT令牌(管理端 / 单用户场景)
|
||||
/// </summary>
|
||||
@ -60,6 +63,21 @@ public static class JwtHelper
|
||||
/// </summary>
|
||||
public const string WxUserIdClaimType = "WxUserId";
|
||||
|
||||
public static string BuildAdminTokenKey(string userId)
|
||||
{
|
||||
return $"{AdminTokenKeyPrefix}:{userId}";
|
||||
}
|
||||
|
||||
public static string BuildWeChatTokenKey(string wxUserId, string userId)
|
||||
{
|
||||
return $"{WeChatTokenKeyPrefix}:{wxUserId}:{userId}";
|
||||
}
|
||||
|
||||
public static string BuildWeChatTokenKey(long wxUserId, long userId)
|
||||
{
|
||||
return BuildWeChatTokenKey(wxUserId.ToString(), userId.ToString());
|
||||
}
|
||||
|
||||
private static string BuildToken(Claim[] claims, JwtSettings settings)
|
||||
{
|
||||
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(settings.SecretKey!));
|
||||
@ -101,7 +119,7 @@ public static class JwtHelper
|
||||
var tokenHandler = new JwtSecurityTokenHandler();
|
||||
if (tokenHandler.ReadToken(token) is JwtSecurityToken jwtToken)
|
||||
{
|
||||
var userIdClaim = jwtToken.Claims.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier);
|
||||
var userIdClaim = GetClaim(jwtToken, ClaimTypes.NameIdentifier, JwtRegisteredClaimNames.NameId, JwtRegisteredClaimNames.Sub);
|
||||
if (long.TryParse(userIdClaim?.Value, out long userId))
|
||||
{
|
||||
return userId;
|
||||
@ -139,8 +157,13 @@ public static class JwtHelper
|
||||
var tokenHandler = new JwtSecurityTokenHandler();
|
||||
if (tokenHandler.ReadToken(token) is JwtSecurityToken jwtToken)
|
||||
{
|
||||
return jwtToken.Claims.FirstOrDefault(c => c.Type == ClaimTypes.Name)?.Value ?? string.Empty;
|
||||
return GetClaim(jwtToken, ClaimTypes.Name, JwtRegisteredClaimNames.UniqueName, JwtRegisteredClaimNames.Name)?.Value ?? string.Empty;
|
||||
}
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
private static Claim? GetClaim(JwtSecurityToken jwtToken, params string[] claimTypes)
|
||||
{
|
||||
return jwtToken.Claims.FirstOrDefault(c => claimTypes.Contains(c.Type));
|
||||
}
|
||||
}
|
||||
|
||||
@ -7,6 +7,7 @@ using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using QYZH.InteractiveMagazine.Infrastructure.Auth;
|
||||
using QYZH.InteractiveMagazine.Infrastructure.Middleware;
|
||||
using QYZH.InteractiveMagazine.Models.Settings;
|
||||
using System.Security.Claims;
|
||||
@ -16,95 +17,137 @@ using System.Text.Encodings.Web;
|
||||
namespace QYZH.InteractiveMagazine.Infrastructure.Extensions;
|
||||
|
||||
/// <summary>
|
||||
/// 统一服务注册扩展
|
||||
/// Infrastructure service registration extensions.
|
||||
/// </summary>
|
||||
public static class DependencyInjectionExtensions
|
||||
{
|
||||
private const string NoAuthScheme = "NoAuth";
|
||||
private const string DevelopmentAuthScheme = "DevelopmentSmartAuth";
|
||||
|
||||
/// <summary>
|
||||
/// 注册基础设施服务
|
||||
/// Registers infrastructure services.
|
||||
/// </summary>
|
||||
/// <param name="services">服务集合</param>
|
||||
/// <param name="configuration">配置</param>
|
||||
/// <param name="environment">运行环境</param>
|
||||
public static void AddInfrastructureServices(this IServiceCollection services, IConfiguration configuration, IWebHostEnvironment? environment = null)
|
||||
{
|
||||
AddJwtAuthentication(services, configuration, environment);
|
||||
|
||||
services.AddTransient<GlobalExceptionMiddleware>();
|
||||
services.AddTransient<OperationLogMiddleware>();
|
||||
|
||||
services.AddScoped<OperationLogActionFilter>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 配置JWT认证
|
||||
/// </summary>
|
||||
/// <param name="services">服务集合</param>
|
||||
/// <param name="configuration">配置</param>
|
||||
/// <param name="environment">运行环境</param>
|
||||
private static void AddJwtAuthentication(IServiceCollection services, IConfiguration configuration, IWebHostEnvironment? environment = null)
|
||||
{
|
||||
// 开发环境下跳过 JWT 验证
|
||||
var jwtSettings = configuration.GetSection("JwtSettings").Get<JwtSettings>()!;
|
||||
services.AddSingleton(jwtSettings);
|
||||
|
||||
if (environment?.IsDevelopment() == true)
|
||||
{
|
||||
services.AddAuthentication("NoAuth")
|
||||
.AddScheme<AuthenticationSchemeOptions, NoAuthHandler>("NoAuth", options => { });
|
||||
services.AddAuthentication(options =>
|
||||
{
|
||||
options.DefaultScheme = DevelopmentAuthScheme;
|
||||
options.DefaultChallengeScheme = DevelopmentAuthScheme;
|
||||
})
|
||||
.AddPolicyScheme(DevelopmentAuthScheme, null, options =>
|
||||
{
|
||||
options.ForwardDefaultSelector = context =>
|
||||
{
|
||||
var authHeader = context.Request.Headers.Authorization.FirstOrDefault();
|
||||
return !string.IsNullOrWhiteSpace(authHeader) &&
|
||||
authHeader.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase)
|
||||
? JwtBearerDefaults.AuthenticationScheme
|
||||
: NoAuthScheme;
|
||||
};
|
||||
})
|
||||
.AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, options => ConfigureJwtBearer(options, jwtSettings))
|
||||
.AddScheme<AuthenticationSchemeOptions, NoAuthHandler>(NoAuthScheme, options => { });
|
||||
return;
|
||||
}
|
||||
|
||||
var jwtSettings = configuration.GetSection("JwtSettings").Get<JwtSettings>()!;
|
||||
|
||||
services.AddSingleton(jwtSettings);
|
||||
|
||||
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
|
||||
.AddJwtBearer(options =>
|
||||
.AddJwtBearer(options => ConfigureJwtBearer(options, jwtSettings));
|
||||
}
|
||||
|
||||
private static void ConfigureJwtBearer(JwtBearerOptions options, JwtSettings jwtSettings)
|
||||
{
|
||||
options.TokenValidationParameters = new TokenValidationParameters
|
||||
{
|
||||
ValidateIssuer = true,
|
||||
ValidIssuer = jwtSettings.Issuer,
|
||||
ValidateAudience = true,
|
||||
ValidAudience = jwtSettings.Audience,
|
||||
ValidateIssuerSigningKey = true,
|
||||
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtSettings.SecretKey!)),
|
||||
ValidateLifetime = true,
|
||||
ClockSkew = TimeSpan.Zero
|
||||
};
|
||||
|
||||
options.Events = new JwtBearerEvents
|
||||
{
|
||||
OnTokenValidated = async context =>
|
||||
{
|
||||
options.TokenValidationParameters = new TokenValidationParameters
|
||||
var currentToken = GetBearerToken(context);
|
||||
if (string.IsNullOrEmpty(currentToken))
|
||||
{
|
||||
ValidateIssuer = true,
|
||||
ValidIssuer = jwtSettings.Issuer,
|
||||
ValidateAudience = true,
|
||||
ValidAudience = jwtSettings.Audience,
|
||||
ValidateIssuerSigningKey = true,
|
||||
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtSettings.SecretKey!)),
|
||||
ValidateLifetime = true,
|
||||
ClockSkew = TimeSpan.Zero
|
||||
};
|
||||
context.Fail("Invalid token");
|
||||
return;
|
||||
}
|
||||
|
||||
options.Events = new JwtBearerEvents
|
||||
var userId = context.Principal?.FindFirst(ClaimTypes.NameIdentifier)?.Value;
|
||||
if (string.IsNullOrEmpty(userId))
|
||||
{
|
||||
OnTokenValidated = async context =>
|
||||
context.Fail("Invalid token");
|
||||
return;
|
||||
}
|
||||
|
||||
var wxUserId = context.Principal?.FindFirst(JwtHelper.WxUserIdClaimType)?.Value;
|
||||
if (string.IsNullOrEmpty(wxUserId))
|
||||
{
|
||||
var adminToken = await RedisHelper.GetAsync(JwtHelper.BuildAdminTokenKey(userId));
|
||||
if (adminToken == currentToken)
|
||||
{
|
||||
var userId = context.Principal?.FindFirst(ClaimTypes.NameIdentifier)?.Value;
|
||||
if (string.IsNullOrEmpty(userId))
|
||||
{
|
||||
context.Fail("无效的 Token");
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查管理后台 Token
|
||||
var adminToken = await RedisHelper.GetAsync($"InteractiveMagazine:AdminAuth:Token:{userId}");
|
||||
if (!string.IsNullOrEmpty(adminToken))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查微信端 Token
|
||||
var wechatToken = await RedisHelper.GetAsync($"InteractiveMagazine:WeChatAuth:Token:{userId}");
|
||||
if (!string.IsNullOrEmpty(wechatToken))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Redis 中不存在任何 Token,认证失败
|
||||
context.Fail("Token 已失效,请重新登录");
|
||||
await RefreshRedisTokenExpiryAsync(JwtHelper.BuildAdminTokenKey(userId), currentToken, jwtSettings);
|
||||
return;
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
context.Fail("Token expired, please login again");
|
||||
return;
|
||||
}
|
||||
|
||||
var wechatToken = await RedisHelper.GetAsync(JwtHelper.BuildWeChatTokenKey(wxUserId, userId));
|
||||
if (wechatToken == currentToken)
|
||||
{
|
||||
await RefreshRedisTokenExpiryAsync(JwtHelper.BuildWeChatTokenKey(wxUserId, userId), currentToken, jwtSettings);
|
||||
return;
|
||||
}
|
||||
|
||||
context.Fail("Token expired, please login again");
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static async Task RefreshRedisTokenExpiryAsync(string tokenKey, string currentToken, JwtSettings jwtSettings)
|
||||
{
|
||||
if (jwtSettings.ExpiryMinutes > 0)
|
||||
{
|
||||
await RedisHelper.SetAsync(tokenKey, currentToken, TimeSpan.FromMinutes(jwtSettings.ExpiryMinutes));
|
||||
}
|
||||
}
|
||||
|
||||
private static string? GetBearerToken(TokenValidatedContext context)
|
||||
{
|
||||
var authHeader = context.HttpContext.Request.Headers.Authorization.FirstOrDefault();
|
||||
if (string.IsNullOrWhiteSpace(authHeader) || !authHeader.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return authHeader.Substring("Bearer ".Length).Trim();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 开发环境免认证处理器
|
||||
/// Authentication handler used only in development.
|
||||
/// </summary>
|
||||
public class NoAuthHandler : AuthenticationHandler<AuthenticationSchemeOptions>
|
||||
{
|
||||
@ -115,14 +158,13 @@ public class NoAuthHandler : AuthenticationHandler<AuthenticationSchemeOptions>
|
||||
|
||||
protected override Task<AuthenticateResult> HandleAuthenticateAsync()
|
||||
{
|
||||
// 开发环境下始终认证成功
|
||||
var claims = new[]
|
||||
{
|
||||
new System.Security.Claims.Claim(System.Security.Claims.ClaimTypes.Name, "DevUser"),
|
||||
new System.Security.Claims.Claim(System.Security.Claims.ClaimTypes.NameIdentifier, "0")
|
||||
new Claim(ClaimTypes.Name, "DevUser"),
|
||||
new Claim(ClaimTypes.NameIdentifier, "0")
|
||||
};
|
||||
var identity = new System.Security.Claims.ClaimsIdentity(claims, Scheme.Name);
|
||||
var principal = new System.Security.Claims.ClaimsPrincipal(identity);
|
||||
var identity = new ClaimsIdentity(claims, Scheme.Name);
|
||||
var principal = new ClaimsPrincipal(identity);
|
||||
var ticket = new AuthenticationTicket(principal, Scheme.Name);
|
||||
|
||||
return Task.FromResult(AuthenticateResult.Success(ticket));
|
||||
|
||||
@ -0,0 +1,86 @@
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.DataProtection;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.ResponseCompression;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using QYZH.InteractiveMagazine.Common.Helpers;
|
||||
using QYZH.InteractiveMagazine.Infrastructure.Middleware;
|
||||
using QYZH.InteractiveMagazine.Infrastructure.RabbitMQ;
|
||||
using QYZH.InteractiveMagazine.Infrastructure.Redis;
|
||||
using QYZH.InteractiveMagazine.Infrastructure.SDK;
|
||||
using QYZH.InteractiveMagazine.Models.Settings;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace QYZH.InteractiveMagazine.Infrastructure.Extensions;
|
||||
|
||||
/// <summary>
|
||||
/// API 默认服务注册扩展。
|
||||
/// </summary>
|
||||
public static class InteractiveMagazineApiDefaultsExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// 注册 WebApi 与 WeChatApi 共享的默认服务。
|
||||
/// </summary>
|
||||
/// <param name="builder">应用构建器。</param>
|
||||
/// <returns>应用构建器。</returns>
|
||||
public static WebApplicationBuilder AddInteractiveMagazineApiDefaults(this WebApplicationBuilder builder)
|
||||
{
|
||||
builder.Services.AddDataProtection()
|
||||
.PersistKeysToFileSystem(new DirectoryInfo(Path.Combine(Directory.GetCurrentDirectory(), "DataProtection")));
|
||||
|
||||
builder.Services.AddCSRedisCacheExtension(builder.Configuration.GetSection("RedisSettings"));
|
||||
builder.Services.AddRabbitMQ(builder.Configuration);
|
||||
|
||||
builder.Services.AddControllers(options =>
|
||||
{
|
||||
options.SuppressImplicitRequiredAttributeForNonNullableReferenceTypes = true;
|
||||
options.Filters.Add<ModelValidActionFilterAttribute>();
|
||||
options.Filters.AddService<OperationLogActionFilter>();
|
||||
})
|
||||
.AddJsonOptions(options =>
|
||||
{
|
||||
options.JsonSerializerOptions.PropertyNameCaseInsensitive = true;
|
||||
options.JsonSerializerOptions.Converters.Add(new JsonConverterUtil.DateTimeConverter());
|
||||
options.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.IgnoreCycles;
|
||||
})
|
||||
.ConfigureApiBehaviorOptions(opt => opt.SuppressModelStateInvalidFilter = true);
|
||||
|
||||
builder.Services.AddEndpointsApiExplorer();
|
||||
builder.AddCorsRegister();
|
||||
builder.Services.AddHttpClient();
|
||||
builder.Services.AddHttpContextAccessor();
|
||||
builder.Services.AddInfrastructureServices(builder.Configuration, builder.Environment);
|
||||
builder.Services.AddSDKService(builder.Configuration);
|
||||
builder.Services.Configure<MedalRuleConfigSettings>(builder.Configuration.GetSection("MedalRuleConfig"));
|
||||
|
||||
builder.Services.AddCors(options =>
|
||||
{
|
||||
options.AddPolicy("AllowAll", policy =>
|
||||
{
|
||||
policy.AllowAnyOrigin()
|
||||
.AllowAnyMethod()
|
||||
.AllowAnyHeader();
|
||||
});
|
||||
});
|
||||
|
||||
builder.Services.Configure<BrotliCompressionProviderOptions>(options =>
|
||||
{
|
||||
options.Level = System.IO.Compression.CompressionLevel.Optimal;
|
||||
});
|
||||
builder.Services.Configure<GzipCompressionProviderOptions>(options =>
|
||||
{
|
||||
options.Level = System.IO.Compression.CompressionLevel.Fastest;
|
||||
});
|
||||
builder.Services.AddResponseCompression(options =>
|
||||
{
|
||||
options.EnableForHttps = true;
|
||||
options.Providers.Add<BrotliCompressionProvider>();
|
||||
options.Providers.Add<GzipCompressionProvider>();
|
||||
options.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat(["image/svg+xml", "application/json", "text/plain"]);
|
||||
});
|
||||
|
||||
return builder;
|
||||
}
|
||||
}
|
||||
@ -13,6 +13,14 @@ public class GlobalExceptionMiddleware : IMiddleware
|
||||
{
|
||||
private readonly ILogger<GlobalExceptionMiddleware> _logger;
|
||||
|
||||
/// <summary>
|
||||
/// JSON序列化选项(camelCase,与Controller保持一致)
|
||||
/// </summary>
|
||||
private static readonly JsonSerializerOptions JsonOptions = new()
|
||||
{
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// </summary>
|
||||
@ -35,7 +43,7 @@ public class GlobalExceptionMiddleware : IMiddleware
|
||||
}
|
||||
catch (BusinessException ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "业务异常:{Message}", ex.Message);
|
||||
_logger.LogWarning(ex, "业务异常:{Message},状态码:{ResultCode}", ex.Message, ex.ResultCode);
|
||||
await HandleBusinessExceptionAsync(context, ex);
|
||||
}
|
||||
catch (Exception ex)
|
||||
@ -46,17 +54,17 @@ public class GlobalExceptionMiddleware : IMiddleware
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理业务异常
|
||||
/// 处理业务异常(按业务状态码映射HTTP状态码)
|
||||
/// </summary>
|
||||
/// <param name="context">HTTP上下文</param>
|
||||
/// <param name="ex">业务异常</param>
|
||||
private static async Task HandleBusinessExceptionAsync(HttpContext context, BusinessException ex)
|
||||
{
|
||||
context.Response.ContentType = "application/json";
|
||||
context.Response.StatusCode = StatusCodes.Status400BadRequest;
|
||||
context.Response.StatusCode = GetHttpStatusCode(ex.ResultCode);
|
||||
|
||||
var response = BaseResponse<object>.Fail(ResultCode.FAIL,ex.Message);
|
||||
var json = JsonSerializer.Serialize(response);
|
||||
var response = BaseResponse<object>.Fail(ex.ResultCode, ex.Message);
|
||||
var json = JsonSerializer.Serialize(response, JsonOptions);
|
||||
|
||||
await context.Response.WriteAsync(json);
|
||||
}
|
||||
@ -71,9 +79,32 @@ public class GlobalExceptionMiddleware : IMiddleware
|
||||
context.Response.ContentType = "application/json";
|
||||
context.Response.StatusCode = StatusCodes.Status500InternalServerError;
|
||||
|
||||
var response = BaseResponse<object>.Fail("系统内部错误,请稍后重试");
|
||||
var json = JsonSerializer.Serialize(response);
|
||||
var response = BaseResponse<object>.Fail(ResultCode.GLOBAL_ERROR, "系统内部错误,请稍后重试");
|
||||
var json = JsonSerializer.Serialize(response, JsonOptions);
|
||||
|
||||
await context.Response.WriteAsync(json);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据业务状态码获取对应的HTTP状态码
|
||||
/// </summary>
|
||||
/// <param name="resultCode">业务状态码</param>
|
||||
/// <returns>HTTP状态码</returns>
|
||||
private static int GetHttpStatusCode(ResultCode resultCode)
|
||||
{
|
||||
return resultCode switch
|
||||
{
|
||||
ResultCode.DENY => StatusCodes.Status401Unauthorized,
|
||||
ResultCode.FORBIDDEN => StatusCodes.Status403Forbidden,
|
||||
ResultCode.NOT_FOUND => StatusCodes.Status404NotFound,
|
||||
ResultCode.BAD_REQUEST => StatusCodes.Status400BadRequest,
|
||||
ResultCode.PARAM_ERROR => StatusCodes.Status400BadRequest,
|
||||
ResultCode.CONFLICT => StatusCodes.Status409Conflict,
|
||||
ResultCode.UNPROCESSABLE_ENTITY => StatusCodes.Status422UnprocessableEntity,
|
||||
ResultCode.TOO_MANY_REQUESTS => StatusCodes.Status429TooManyRequests,
|
||||
ResultCode.BAD_GATEWAY => StatusCodes.Status502BadGateway,
|
||||
ResultCode.SERVICE_UNAVAILABLE => StatusCodes.Status503ServiceUnavailable,
|
||||
_ => StatusCodes.Status400BadRequest
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,101 +1,21 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using QYZH.InteractiveMagazine.Models.Settings;
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
|
||||
namespace QYZH.InteractiveMagazine.Infrastructure.Middleware;
|
||||
|
||||
/// <summary>
|
||||
/// JWT 自动刷新中间件
|
||||
/// 功能:
|
||||
/// 每次请求都刷新 Redis 中 Token 的过期时间(保持会话活跃)
|
||||
/// 前端永远使用同一个 Token,无需处理 Token 刷新
|
||||
/// 保留兼容的JWT自动刷新中间件,实际刷新在认证成功后执行。
|
||||
/// </summary>
|
||||
public class JwtAutoRefreshMiddleware
|
||||
{
|
||||
private readonly RequestDelegate _next;
|
||||
private readonly IConfiguration _configuration;
|
||||
private const string AdminTokenKeyPrefix = "InteractiveMagazine:AdminAuth:Token";
|
||||
private const string WeChatTokenKeyPrefix = "InteractiveMagazine:WeChatAuth:Token";
|
||||
|
||||
public JwtAutoRefreshMiddleware(RequestDelegate next, IConfiguration configuration)
|
||||
public JwtAutoRefreshMiddleware(RequestDelegate next)
|
||||
{
|
||||
_next = next;
|
||||
_configuration = configuration;
|
||||
}
|
||||
|
||||
public async Task InvokeAsync(HttpContext context)
|
||||
{
|
||||
var authHeader = context.Request.Headers.Authorization.FirstOrDefault();
|
||||
if (!string.IsNullOrEmpty(authHeader) && authHeader.StartsWith("Bearer "))
|
||||
{
|
||||
var token = authHeader.Substring("Bearer ".Length).Trim();
|
||||
await TryRefreshRedisTokenExpiryAsync(context, token);
|
||||
}
|
||||
|
||||
await _next(context);
|
||||
}
|
||||
|
||||
private async Task TryRefreshRedisTokenExpiryAsync(HttpContext context, string token)
|
||||
{
|
||||
try
|
||||
{
|
||||
var tokenHandler = new JwtSecurityTokenHandler();
|
||||
if (tokenHandler.ReadToken(token) is not JwtSecurityToken jwtToken)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var expiryTime = jwtToken.ValidTo;
|
||||
var remainingTime = expiryTime - DateTime.Now;
|
||||
|
||||
// JWT 已过期,不处理
|
||||
if (remainingTime <= TimeSpan.Zero)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var userId = jwtToken.Claims.FirstOrDefault(c => c.Type == System.Security.Claims.ClaimTypes.NameIdentifier)?.Value;
|
||||
if (string.IsNullOrEmpty(userId))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var jwtSettings = _configuration.GetSection("JwtSettings").Get<JwtSettings>();
|
||||
if (jwtSettings == null || jwtSettings.ExpiryMinutes <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// 每次请求都刷新 Redis 中 Token 的过期时间
|
||||
await RefreshRedisTokenExpiryAsync(userId, token, jwtSettings.ExpiryMinutes);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// 忽略自动刷新异常,由后续认证中间件处理
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 刷新 Redis 中已存在 Token 的过期时间(保持会话活跃)
|
||||
/// </summary>
|
||||
private async Task RefreshRedisTokenExpiryAsync(string userId, string currentToken, int expiryMinutes)
|
||||
{
|
||||
// 检查 Admin Token
|
||||
var adminTokenKey = $"{AdminTokenKeyPrefix}:{userId}";
|
||||
var adminToken = await RedisHelper.GetAsync(adminTokenKey);
|
||||
if (!string.IsNullOrEmpty(adminToken))
|
||||
{
|
||||
await RedisHelper.SetAsync(adminTokenKey, currentToken, TimeSpan.FromMinutes(expiryMinutes));
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查 WeChat Token
|
||||
var wechatTokenKey = $"{WeChatTokenKeyPrefix}:{userId}";
|
||||
var wechatToken = await RedisHelper.GetAsync(wechatTokenKey);
|
||||
if (!string.IsNullOrEmpty(wechatToken))
|
||||
{
|
||||
await RedisHelper.SetAsync(wechatTokenKey, currentToken, TimeSpan.FromMinutes(expiryMinutes));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -24,7 +24,7 @@ public class ModelValidActionFilterAttribute : ActionFilterAttribute
|
||||
errorDic.Add(key, errorStr);
|
||||
}
|
||||
}
|
||||
var result = new BaseResponse<Dictionary<string, string>>() { code = ResultCode.FAIL };
|
||||
var result = new BaseResponse<Dictionary<string, string>>() { code = ResultCode.BAD_REQUEST };
|
||||
|
||||
result.message = string.Join("|", errorDic.Select(e => e.Value).Distinct());
|
||||
result.result = errorDic;
|
||||
|
||||
@ -0,0 +1,397 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using QYZH.InteractiveMagazine.IService;
|
||||
using QYZH.InteractiveMagazine.Models.Dto;
|
||||
using System.Collections;
|
||||
using System.Diagnostics;
|
||||
using System.Reflection;
|
||||
using System.Security.Claims;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace QYZH.InteractiveMagazine.Infrastructure.Middleware;
|
||||
|
||||
/// <summary>
|
||||
/// 操作日志过滤器
|
||||
/// </summary>
|
||||
public class OperationLogActionFilter(
|
||||
IOperationLogService operationLogService,
|
||||
ILogger<OperationLogActionFilter> logger) : IAsyncActionFilter
|
||||
{
|
||||
private static readonly HashSet<string> SensitiveNames = new(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
"password",
|
||||
"oldPassword",
|
||||
"newPassword",
|
||||
"token",
|
||||
"secret",
|
||||
"authorization"
|
||||
};
|
||||
|
||||
private static readonly JsonSerializerOptions JsonOptions = new()
|
||||
{
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||
ReferenceHandler = ReferenceHandler.IgnoreCycles
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// 执行操作日志过滤器
|
||||
/// </summary>
|
||||
/// <param name="context">Action 执行上下文</param>
|
||||
/// <param name="next">后续执行委托</param>
|
||||
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
|
||||
{
|
||||
var attribute = context.ActionDescriptor.EndpointMetadata
|
||||
.OfType<OperationLogAttribute>()
|
||||
.FirstOrDefault();
|
||||
|
||||
if (attribute == null)
|
||||
{
|
||||
await next();
|
||||
return;
|
||||
}
|
||||
|
||||
var stopwatch = Stopwatch.StartNew();
|
||||
var executedContext = await next();
|
||||
stopwatch.Stop();
|
||||
|
||||
if (executedContext.Exception != null && !executedContext.ExceptionHandled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!IsSuccessResult(executedContext.Result, context.HttpContext.Response.StatusCode))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var operatorId = GetOperatorId(context);
|
||||
if (!operatorId.HasValue)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var operatorName = GetOperatorName(context);
|
||||
var responseValue = GetResponseValue(executedContext.Result);
|
||||
var responseResult = GetResponseResult(responseValue);
|
||||
var targetId = ResolveTargetId(attribute, context, responseResult, operatorId.Value);
|
||||
var targetName = ResolveTargetName(attribute, context, responseResult);
|
||||
|
||||
var detail = BuildDetail(attribute, context, responseValue, stopwatch.ElapsedMilliseconds);
|
||||
|
||||
await operationLogService.LogAsync(new OperationLogRecordInput
|
||||
{
|
||||
OperatorId = operatorId.Value,
|
||||
OperatorName = operatorName,
|
||||
ActionType = attribute.ActionType,
|
||||
TargetType = attribute.TargetType,
|
||||
TargetId = targetId,
|
||||
TargetName = targetName,
|
||||
Detail = detail,
|
||||
IpAddress = GetClientIp(context)
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "自动记录操作日志失败,Path: {Path}", context.HttpContext.Request.Path);
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsSuccessResult(IActionResult? result, int responseStatusCode)
|
||||
{
|
||||
var responseValue = GetResponseValue(result);
|
||||
if (responseValue is BaseResponse response)
|
||||
{
|
||||
return response.isSuccess;
|
||||
}
|
||||
|
||||
if (result is ObjectResult objectResult && objectResult.StatusCode.HasValue)
|
||||
{
|
||||
return IsSuccessStatusCode(objectResult.StatusCode.Value);
|
||||
}
|
||||
|
||||
if (result is StatusCodeResult statusCodeResult)
|
||||
{
|
||||
return IsSuccessStatusCode(statusCodeResult.StatusCode);
|
||||
}
|
||||
|
||||
return IsSuccessStatusCode(responseStatusCode == 0 ? StatusCodes.Status200OK : responseStatusCode);
|
||||
}
|
||||
|
||||
private static bool IsSuccessStatusCode(int statusCode)
|
||||
{
|
||||
return statusCode >= StatusCodes.Status200OK && statusCode < StatusCodes.Status300MultipleChoices;
|
||||
}
|
||||
|
||||
private static long? GetOperatorId(ActionExecutingContext context)
|
||||
{
|
||||
var value = context.HttpContext.User.Claims
|
||||
.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier)
|
||||
?.Value;
|
||||
|
||||
return long.TryParse(value, out var operatorId) ? operatorId : null;
|
||||
}
|
||||
|
||||
private static string GetOperatorName(ActionExecutingContext context)
|
||||
{
|
||||
return context.HttpContext.User.Claims
|
||||
.FirstOrDefault(c => c.Type == ClaimTypes.Name)
|
||||
?.Value ?? string.Empty;
|
||||
}
|
||||
|
||||
private static string? GetClientIp(ActionExecutingContext context)
|
||||
{
|
||||
var request = context.HttpContext.Request;
|
||||
var forwardedFor = request.Headers["X-Forwarded-For"].FirstOrDefault();
|
||||
if (!string.IsNullOrWhiteSpace(forwardedFor))
|
||||
{
|
||||
return forwardedFor.Split(',')[0].Trim();
|
||||
}
|
||||
|
||||
var realIp = request.Headers["X-Real-IP"].FirstOrDefault();
|
||||
if (!string.IsNullOrWhiteSpace(realIp))
|
||||
{
|
||||
return realIp;
|
||||
}
|
||||
|
||||
return context.HttpContext.Connection.RemoteIpAddress?.ToString();
|
||||
}
|
||||
|
||||
private static long ResolveTargetId(OperationLogAttribute attribute, ActionExecutingContext context, object? responseResult, long operatorId)
|
||||
{
|
||||
if (attribute.UseOperatorAsTargetId)
|
||||
{
|
||||
return operatorId;
|
||||
}
|
||||
|
||||
if (TryGetLongRouteValue(context, attribute.TargetIdRouteKey, out var routeId))
|
||||
{
|
||||
return routeId;
|
||||
}
|
||||
|
||||
if (TryGetLongArgumentValue(context, attribute.TargetIdArgumentName, out var argumentId))
|
||||
{
|
||||
return argumentId;
|
||||
}
|
||||
|
||||
foreach (var key in new[] { "id", "userId", "adminUserId", "roleId", "taskId", "recordId" })
|
||||
{
|
||||
if (TryGetLongRouteValue(context, key, out routeId) || TryGetLongArgumentValue(context, key, out argumentId))
|
||||
{
|
||||
return routeId != 0 ? routeId : argumentId;
|
||||
}
|
||||
}
|
||||
|
||||
if (TryConvertToLong(responseResult, out var responseId))
|
||||
{
|
||||
return responseId;
|
||||
}
|
||||
|
||||
return TryGetLongPropertyValue(responseResult, "Id", out responseId) ? responseId : 0;
|
||||
}
|
||||
|
||||
private static string? ResolveTargetName(OperationLogAttribute attribute, ActionExecutingContext context, object? responseResult)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(attribute.TargetNameArgumentName)
|
||||
&& context.ActionArguments.TryGetValue(attribute.TargetNameArgumentName, out var nameArgument))
|
||||
{
|
||||
if (nameArgument is string name)
|
||||
{
|
||||
return name;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(attribute.TargetNameProperty)
|
||||
&& TryGetStringPropertyValue(nameArgument, attribute.TargetNameProperty, out name))
|
||||
{
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var value in context.ActionArguments.Values)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(attribute.TargetNameProperty)
|
||||
&& TryGetStringPropertyValue(value, attribute.TargetNameProperty, out var configuredName))
|
||||
{
|
||||
return configuredName;
|
||||
}
|
||||
|
||||
if (TryGetStringPropertyValue(value, "Name", out var name)
|
||||
|| TryGetStringPropertyValue(value, "Title", out name))
|
||||
{
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(attribute.TargetNameProperty)
|
||||
&& TryGetStringPropertyValue(responseResult, attribute.TargetNameProperty, out var responseName))
|
||||
{
|
||||
return responseName;
|
||||
}
|
||||
|
||||
return TryGetStringPropertyValue(responseResult, "Name", out var defaultName)
|
||||
|| TryGetStringPropertyValue(responseResult, "Title", out defaultName)
|
||||
? defaultName
|
||||
: null;
|
||||
}
|
||||
|
||||
private static string BuildDetail(OperationLogAttribute attribute, ActionExecutingContext context, object? responseValue, long elapsedMilliseconds)
|
||||
{
|
||||
var detail = new Dictionary<string, object?>
|
||||
{
|
||||
["method"] = context.HttpContext.Request.Method,
|
||||
["path"] = context.HttpContext.Request.Path.Value,
|
||||
["routeValues"] = context.RouteData.Values.ToDictionary(k => k.Key, v => v.Value?.ToString()),
|
||||
["arguments"] = attribute.LogArguments ? SanitizeValue(context.ActionArguments, 0) : null,
|
||||
["responseMessage"] = GetResponseMessage(responseValue),
|
||||
["elapsedMilliseconds"] = elapsedMilliseconds
|
||||
};
|
||||
|
||||
return JsonSerializer.Serialize(detail, JsonOptions);
|
||||
}
|
||||
|
||||
private static string? GetResponseMessage(object? responseValue)
|
||||
{
|
||||
return responseValue is BaseResponse response ? response.message : null;
|
||||
}
|
||||
|
||||
private static object? GetResponseValue(IActionResult? result)
|
||||
{
|
||||
return result switch
|
||||
{
|
||||
ObjectResult objectResult => objectResult.Value,
|
||||
JsonResult jsonResult => jsonResult.Value,
|
||||
_ => null
|
||||
};
|
||||
}
|
||||
|
||||
private static object? GetResponseResult(object? responseValue)
|
||||
{
|
||||
if (responseValue == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return responseValue.GetType()
|
||||
.GetProperty("result", BindingFlags.Instance | BindingFlags.Public | BindingFlags.IgnoreCase)
|
||||
?.GetValue(responseValue);
|
||||
}
|
||||
|
||||
private static object? SanitizeValue(object? value, int depth)
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (depth > 4)
|
||||
{
|
||||
return value.ToString();
|
||||
}
|
||||
|
||||
var type = value.GetType();
|
||||
if (type.IsPrimitive || value is string or decimal or DateTime or DateTimeOffset or Guid || type.IsEnum)
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
if (value is IDictionary dictionary)
|
||||
{
|
||||
var result = new Dictionary<string, object?>();
|
||||
foreach (DictionaryEntry item in dictionary)
|
||||
{
|
||||
var key = item.Key?.ToString() ?? string.Empty;
|
||||
result[key] = SensitiveNames.Contains(key) ? "***" : SanitizeValue(item.Value, depth + 1);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
if (value is IEnumerable enumerable && value is not string)
|
||||
{
|
||||
return enumerable.Cast<object?>()
|
||||
.Take(20)
|
||||
.Select(item => SanitizeValue(item, depth + 1))
|
||||
.ToList();
|
||||
}
|
||||
|
||||
return type.GetProperties(BindingFlags.Instance | BindingFlags.Public)
|
||||
.Where(p => p.GetIndexParameters().Length == 0)
|
||||
.ToDictionary(
|
||||
p => p.Name,
|
||||
p => SensitiveNames.Contains(p.Name) ? "***" : SanitizeValue(p.GetValue(value), depth + 1));
|
||||
}
|
||||
|
||||
private static bool TryGetLongRouteValue(ActionExecutingContext context, string? key, out long value)
|
||||
{
|
||||
value = 0;
|
||||
if (string.IsNullOrWhiteSpace(key) || !context.RouteData.Values.TryGetValue(key, out var routeValue))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return long.TryParse(routeValue?.ToString(), out value);
|
||||
}
|
||||
|
||||
private static bool TryGetLongArgumentValue(ActionExecutingContext context, string? key, out long value)
|
||||
{
|
||||
value = 0;
|
||||
if (string.IsNullOrWhiteSpace(key) || !context.ActionArguments.TryGetValue(key, out var argumentValue))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (TryConvertToLong(argumentValue, out value))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return TryGetLongPropertyValue(argumentValue, "Id", out value);
|
||||
}
|
||||
|
||||
private static bool TryGetLongPropertyValue(object? source, string propertyName, out long value)
|
||||
{
|
||||
value = 0;
|
||||
if (source == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var property = source.GetType().GetProperty(propertyName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.IgnoreCase);
|
||||
return property != null && TryConvertToLong(property.GetValue(source), out value);
|
||||
}
|
||||
|
||||
private static bool TryConvertToLong(object? source, out long value)
|
||||
{
|
||||
value = 0;
|
||||
return source switch
|
||||
{
|
||||
long longValue => SetValue(longValue, out value),
|
||||
int intValue => SetValue(intValue, out value),
|
||||
string stringValue => long.TryParse(stringValue, out value),
|
||||
_ => long.TryParse(source?.ToString(), out value)
|
||||
};
|
||||
}
|
||||
|
||||
private static bool TryGetStringPropertyValue(object? source, string propertyName, out string? value)
|
||||
{
|
||||
value = null;
|
||||
if (source == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var property = source.GetType().GetProperty(propertyName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.IgnoreCase);
|
||||
value = property?.GetValue(source)?.ToString();
|
||||
return !string.IsNullOrWhiteSpace(value);
|
||||
}
|
||||
|
||||
private static bool SetValue(long source, out long value)
|
||||
{
|
||||
value = source;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,59 @@
|
||||
namespace QYZH.InteractiveMagazine.Infrastructure.Middleware;
|
||||
|
||||
/// <summary>
|
||||
/// 操作日志标记
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Method)]
|
||||
public sealed class OperationLogAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// </summary>
|
||||
/// <param name="actionType">操作类型</param>
|
||||
/// <param name="targetType">目标类型</param>
|
||||
public OperationLogAttribute(string actionType, string targetType)
|
||||
{
|
||||
ActionType = actionType;
|
||||
TargetType = targetType;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 操作类型
|
||||
/// </summary>
|
||||
public string ActionType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 目标类型
|
||||
/// </summary>
|
||||
public string TargetType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 目标Id路由键
|
||||
/// </summary>
|
||||
public string? TargetIdRouteKey { get; set; } = "id";
|
||||
|
||||
/// <summary>
|
||||
/// 目标Id参数名
|
||||
/// </summary>
|
||||
public string? TargetIdArgumentName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 目标名称参数名
|
||||
/// </summary>
|
||||
public string? TargetNameArgumentName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 目标名称属性名
|
||||
/// </summary>
|
||||
public string? TargetNameProperty { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 使用当前操作人Id作为目标Id
|
||||
/// </summary>
|
||||
public bool UseOperatorAsTargetId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否记录参数摘要
|
||||
/// </summary>
|
||||
public bool LogArguments { get; set; } = true;
|
||||
}
|
||||
@ -1,6 +1,7 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using QYZH.InteractiveMagazine.Common.Extensions;
|
||||
using QYZH.InteractiveMagazine.Models.Common;
|
||||
using QYZH.InteractiveMagazine.Models.Dto;
|
||||
|
||||
namespace QYZH.InteractiveMagazine.Infrastructure.OSS;
|
||||
|
||||
@ -87,7 +88,7 @@ public class OssImageHelper
|
||||
catch (Exception ex) when (ex is not BusinessException)
|
||||
{
|
||||
_logger.LogError(ex, "OSS 图片搬运异常,Source: {Source}, Target: {Target}", sourcePath, targetPath);
|
||||
throw new BusinessException("图片搬运失败,请重试", 500);
|
||||
throw new BusinessException("图片搬运失败,请重试", ResultCode.GLOBAL_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -7,6 +7,8 @@ using CSRedis;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using QYZH.InteractiveMagazine.Common.Extensions;
|
||||
using QYZH.InteractiveMagazine.Models.Common;
|
||||
using QYZH.InteractiveMagazine.Models.Dto;
|
||||
using System.Text.Json;
|
||||
using System.Web;
|
||||
|
||||
@ -193,6 +195,53 @@ namespace QYZH.InteractiveMagazine.Infrastructure.OSS
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判断对象是否存在
|
||||
/// </summary>
|
||||
/// <param name="key"></param>
|
||||
/// <returns></returns>
|
||||
public bool DoesObjectExist(string key)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (key.IsNull()) return false;
|
||||
|
||||
key = key.RemoveDomain();
|
||||
key = HttpUtility.UrlDecode(key);
|
||||
|
||||
return _ossClient.DoesObjectExist(_ossOption.BucketName, key);
|
||||
}
|
||||
catch (OssException ex)
|
||||
{
|
||||
_logger.LogError(ex.Message + ex.StackTrace);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取对象内容流
|
||||
/// </summary>
|
||||
/// <param name="key"></param>
|
||||
/// <returns></returns>
|
||||
public Stream? GetObjectStream(string key)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (key.IsNull()) return null;
|
||||
|
||||
key = key.RemoveDomain();
|
||||
key = HttpUtility.UrlDecode(key);
|
||||
|
||||
var ossObject = _ossClient.GetObject(_ossOption.BucketName, key);
|
||||
return ossObject.Content;
|
||||
}
|
||||
catch (OssException ex)
|
||||
{
|
||||
_logger.LogError(ex.Message + ex.StackTrace);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 删除多个
|
||||
/// </summary>
|
||||
|
||||
@ -9,6 +9,6 @@ namespace QYZH.InteractiveMagazine.Infrastructure.RabbitMQ
|
||||
|
||||
Task<bool> SendBatchAsync(IEnumerable<RabbitMQSendParam> @params, CancellationToken cancellationToken = default);
|
||||
|
||||
Task ReceiveAsync(string queueName, Func<IChannel, BasicDeliverEventArgs, Task> callback, CancellationToken cancellationToken = default);
|
||||
Task ReceiveAsync(string exchange, string queueName, string routingKey, Func<IChannel, BasicDeliverEventArgs, Task> callback, CancellationToken cancellationToken = default);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
using RabbitMQ.Client;
|
||||
using RabbitMQ.Client.Events;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System.Text;
|
||||
using System.Text.Encodings.Web;
|
||||
using System.Text.Json;
|
||||
@ -9,13 +11,17 @@ namespace QYZH.InteractiveMagazine.Infrastructure.RabbitMQ
|
||||
public class RabbitMQService : IRabbitMQService
|
||||
{
|
||||
private readonly IRabbitMQConnection _connection;
|
||||
private readonly IConfiguration _configuration;
|
||||
private readonly ILogger<RabbitMQService> _logger;
|
||||
private readonly JsonSerializerOptions options = new JsonSerializerOptions
|
||||
{
|
||||
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
|
||||
};
|
||||
public RabbitMQService(IRabbitMQConnection connection)
|
||||
public RabbitMQService(IRabbitMQConnection connection, IConfiguration configuration, ILogger<RabbitMQService> logger)
|
||||
{
|
||||
_connection = connection ?? throw new ArgumentNullException(nameof(connection));
|
||||
_configuration = configuration;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
|
||||
@ -26,18 +32,17 @@ namespace QYZH.InteractiveMagazine.Infrastructure.RabbitMQ
|
||||
{
|
||||
using var channel = await _connection.CreateChannel();
|
||||
|
||||
// 启用发布者确认
|
||||
//var channelOpts = new CreateChannelOptions
|
||||
//(
|
||||
// publisherConfirmationsEnabled: true,
|
||||
// publisherConfirmationTrackingEnabled: true,
|
||||
// outstandingPublisherConfirmationsRateLimiter: new ThrottlingRateLimiter(MAX_OUTSTANDING_CONFIRMS)
|
||||
//);
|
||||
// 声明 Exchange(持久化)
|
||||
await channel.ExchangeDeclareAsync(exchange: param.Exchange, type: "direct", durable: true, autoDelete: false, arguments: null);
|
||||
|
||||
// 声明队列(持久化)
|
||||
await channel.QueueDeclareAsync(queue: param.RoutingKey, durable: true, exclusive: false, autoDelete: false, arguments: null);
|
||||
await channel.QueueDeclareAsync(queue: param.Queue, durable: true, exclusive: false, autoDelete: false, arguments: null);
|
||||
|
||||
// 绑定队列到 Exchange
|
||||
await channel.QueueBindAsync(queue: param.Queue, exchange: param.Exchange, routingKey: param.RoutingKey, arguments: null);
|
||||
|
||||
// 清空队列
|
||||
if (param.Purge) await channel.QueuePurgeAsync(param.RoutingKey);
|
||||
if (param.Purge) await channel.QueuePurgeAsync(param.Queue);
|
||||
// 消息序列化
|
||||
var mesjson = JsonSerializer.Serialize(param.Data, options);
|
||||
|
||||
@ -53,12 +58,14 @@ namespace QYZH.InteractiveMagazine.Infrastructure.RabbitMQ
|
||||
}
|
||||
catch (OperationCanceledException ex)
|
||||
{
|
||||
Console.WriteLine($"Operation was canceled: {ex.Message}");
|
||||
_logger.LogWarning(ex, "RabbitMQ消息发送已取消,Exchange: {Exchange}, Queue: {Queue}, RoutingKey: {RoutingKey}",
|
||||
param.Exchange, param.Queue, param.RoutingKey);
|
||||
return false;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"An error occurred: {ex.Message}");
|
||||
_logger.LogError(ex, "RabbitMQ消息发送失败,Exchange: {Exchange}, Queue: {Queue}, RoutingKey: {RoutingKey}",
|
||||
param.Exchange, param.Queue, param.RoutingKey);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -113,7 +120,7 @@ namespace QYZH.InteractiveMagazine.Infrastructure.RabbitMQ
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"An error occurred: {ex.Message}");
|
||||
_logger.LogError(ex, "RabbitMQ批量消息发送失败");
|
||||
// 回滚事务
|
||||
try { await channel?.TxRollbackAsync(); } catch { /* 忽略回滚异常 */ }
|
||||
return false;
|
||||
@ -128,12 +135,26 @@ namespace QYZH.InteractiveMagazine.Infrastructure.RabbitMQ
|
||||
}
|
||||
|
||||
|
||||
public async Task ReceiveAsync(string queueName, Func<IChannel, BasicDeliverEventArgs, Task> callback, CancellationToken cancellationToken = default)
|
||||
public async Task ReceiveAsync(string exchange, string queueName, string routingKey, Func<IChannel, BasicDeliverEventArgs, Task> callback, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var channel = await _connection.CreateChannel();
|
||||
//await channel.BasicQosAsync(0, 10, false); // 一次最多接收10条未确认的消息
|
||||
var prefetchCount = _configuration.GetValue<ushort>("RabbitMq:PrefetchCount");
|
||||
if (prefetchCount == 0)
|
||||
{
|
||||
prefetchCount = 1;
|
||||
}
|
||||
|
||||
await channel.BasicQosAsync(0, prefetchCount, false, cancellationToken);
|
||||
|
||||
// 声明 Exchange(持久化)
|
||||
await channel.ExchangeDeclareAsync(exchange: exchange, type: "direct", durable: true, autoDelete: false, arguments: null);
|
||||
|
||||
// 声明队列(持久化)
|
||||
await channel.QueueDeclareAsync(queue: queueName, durable: true, exclusive: false, autoDelete: false, arguments: null);
|
||||
|
||||
// 绑定队列到 Exchange
|
||||
await channel.QueueBindAsync(queue: queueName, exchange: exchange, routingKey: routingKey, arguments: null);
|
||||
|
||||
var consumer = new AsyncEventingBasicConsumer(channel);
|
||||
consumer.ReceivedAsync += async (model, ea) =>
|
||||
{
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
using QYZH.InteractiveMagazine.Models.Dto;
|
||||
|
||||
namespace QYZH.InteractiveMagazine.Models.Common;
|
||||
|
||||
/// <summary>
|
||||
@ -10,6 +12,11 @@ public class BusinessException : Exception
|
||||
/// </summary>
|
||||
public int Code { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 业务状态码
|
||||
/// </summary>
|
||||
public ResultCode ResultCode { get; set; } = ResultCode.FAIL;
|
||||
|
||||
/// <summary>
|
||||
/// 无参构造函数
|
||||
/// </summary>
|
||||
@ -33,6 +40,18 @@ public class BusinessException : Exception
|
||||
public BusinessException(string message, int code) : base(message)
|
||||
{
|
||||
Code = code;
|
||||
ResultCode = System.Enum.IsDefined(typeof(ResultCode), code) ? (ResultCode)code : ResultCode.FAIL;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数(按业务状态码)
|
||||
/// </summary>
|
||||
/// <param name="message">异常消息</param>
|
||||
/// <param name="resultCode">业务状态码</param>
|
||||
public BusinessException(string message, ResultCode resultCode) : base(message)
|
||||
{
|
||||
ResultCode = resultCode;
|
||||
Code = (int)resultCode;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -62,4 +81,15 @@ public class BusinessException : Exception
|
||||
throw new BusinessException(message);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 条件抛出业务异常(带业务状态码)
|
||||
/// </summary>
|
||||
/// <param name="isTrue">是否触发</param>
|
||||
/// <param name="message">异常消息</param>
|
||||
/// <param name="resultCode">业务状态码</param>
|
||||
public static void ThrowIf(bool isTrue, string message, ResultCode resultCode)
|
||||
{
|
||||
if (isTrue) throw new BusinessException(message, resultCode);
|
||||
}
|
||||
}
|
||||
|
||||
292
QYZH.InteractiveMagazine.Models/Dto/Admin/AdminPermissionDto.cs
Normal file
292
QYZH.InteractiveMagazine.Models/Dto/Admin/AdminPermissionDto.cs
Normal file
@ -0,0 +1,292 @@
|
||||
using QYZH.InteractiveMagazine.Models.Enum;
|
||||
|
||||
namespace QYZH.InteractiveMagazine.Models.Dto;
|
||||
|
||||
/// <summary>
|
||||
/// 后台菜单输入
|
||||
/// </summary>
|
||||
public class AdminMenuInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 父级菜单ID,根节点为0
|
||||
/// </summary>
|
||||
public long ParentId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 菜单名称
|
||||
/// </summary>
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 权限编码
|
||||
/// </summary>
|
||||
public string Code { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 前端路由路径
|
||||
/// </summary>
|
||||
public string? Path { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 前端组件路径
|
||||
/// </summary>
|
||||
public string? Component { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 菜单图标
|
||||
/// </summary>
|
||||
public string? Icon { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 排序值
|
||||
/// </summary>
|
||||
public int Sort { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否显示在菜单
|
||||
/// </summary>
|
||||
public bool IsVisible { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// 状态
|
||||
/// </summary>
|
||||
public int Status { get; set; } = 1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 后台菜单输出
|
||||
/// </summary>
|
||||
public class AdminMenuOutput
|
||||
{
|
||||
/// <summary>
|
||||
/// 菜单ID
|
||||
/// </summary>
|
||||
public long Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 父级菜单ID
|
||||
/// </summary>
|
||||
public long ParentId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 菜单名称
|
||||
/// </summary>
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 权限编码
|
||||
/// </summary>
|
||||
public string Code { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 前端路由路径
|
||||
/// </summary>
|
||||
public string? Path { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 前端组件路径
|
||||
/// </summary>
|
||||
public string? Component { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 菜单图标
|
||||
/// </summary>
|
||||
public string? Icon { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 排序值
|
||||
/// </summary>
|
||||
public int Sort { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否显示在菜单
|
||||
/// </summary>
|
||||
public bool IsVisible { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 状态
|
||||
/// </summary>
|
||||
public int Status { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 子菜单
|
||||
/// </summary>
|
||||
public List<AdminMenuOutput> Children { get; set; } = [];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 后台菜单查询输入
|
||||
/// </summary>
|
||||
public class AdminMenuQueryInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 菜单名称
|
||||
/// </summary>
|
||||
public string? Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 权限编码
|
||||
/// </summary>
|
||||
public string? Code { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 状态
|
||||
/// </summary>
|
||||
public int? Status { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 后台角色输入
|
||||
/// </summary>
|
||||
public class AdminRoleInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 角色名称
|
||||
/// </summary>
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 角色编码
|
||||
/// </summary>
|
||||
public string Code { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 角色类型
|
||||
/// </summary>
|
||||
public AdminRoleTypeEnum Type { get; set; } = AdminRoleTypeEnum.Normal;
|
||||
|
||||
/// <summary>
|
||||
/// 备注
|
||||
/// </summary>
|
||||
public string? Remark { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 状态
|
||||
/// </summary>
|
||||
public int Status { get; set; } = 1;
|
||||
|
||||
/// <summary>
|
||||
/// 菜单ID集合
|
||||
/// </summary>
|
||||
public List<long> MenuIds { get; set; } = [];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 后台角色输出
|
||||
/// </summary>
|
||||
public class AdminRoleOutput
|
||||
{
|
||||
/// <summary>
|
||||
/// 角色ID
|
||||
/// </summary>
|
||||
public long Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 角色名称
|
||||
/// </summary>
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 角色编码
|
||||
/// </summary>
|
||||
public string Code { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 角色类型
|
||||
/// </summary>
|
||||
public AdminRoleTypeEnum Type { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 备注
|
||||
/// </summary>
|
||||
public string? Remark { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 状态
|
||||
/// </summary>
|
||||
public int Status { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 菜单ID集合
|
||||
/// </summary>
|
||||
public List<long> MenuIds { get; set; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// 创建时间
|
||||
/// </summary>
|
||||
public DateTime CreatedAt { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 后台角色简要输出
|
||||
/// </summary>
|
||||
public class AdminRoleSimpleOutput
|
||||
{
|
||||
/// <summary>
|
||||
/// 角色ID
|
||||
/// </summary>
|
||||
public long Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 角色名称
|
||||
/// </summary>
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 角色编码
|
||||
/// </summary>
|
||||
public string Code { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 角色类型
|
||||
/// </summary>
|
||||
public AdminRoleTypeEnum Type { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 后台角色查询输入
|
||||
/// </summary>
|
||||
public class AdminRoleQueryInput : PageQueryModel
|
||||
{
|
||||
/// <summary>
|
||||
/// 角色名称
|
||||
/// </summary>
|
||||
public string? Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 角色编码
|
||||
/// </summary>
|
||||
public string? Code { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 角色类型
|
||||
/// </summary>
|
||||
public AdminRoleTypeEnum? Type { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 状态
|
||||
/// </summary>
|
||||
public int? Status { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 分配角色菜单输入
|
||||
/// </summary>
|
||||
public class AssignRoleMenusInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 菜单ID集合
|
||||
/// </summary>
|
||||
public List<long> MenuIds { get; set; } = [];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 分配用户角色输入
|
||||
/// </summary>
|
||||
public class AssignAdminUserRolesInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 角色ID集合
|
||||
/// </summary>
|
||||
public List<long> RoleIds { get; set; } = [];
|
||||
}
|
||||
@ -27,6 +27,11 @@ public class AdminUserInput
|
||||
/// 状态: Active, Inactive
|
||||
/// </summary>
|
||||
public int Status { get; set; } = 1;
|
||||
|
||||
/// <summary>
|
||||
/// 角色ID集合,更新时为null表示不调整角色
|
||||
/// </summary>
|
||||
public List<long>? RoleIds { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -73,6 +78,16 @@ public class AdminUserOutput
|
||||
/// 更新时间
|
||||
/// </summary>
|
||||
public DateTime? UpdatedAt { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 角色ID集合
|
||||
/// </summary>
|
||||
public List<long> RoleIds { get; set; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// 角色集合
|
||||
/// </summary>
|
||||
public List<AdminRoleSimpleOutput> Roles { get; set; } = [];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@ -26,6 +26,26 @@ public class AdminLoginOutput
|
||||
public string UserName { get; set; } = string.Empty;
|
||||
|
||||
public string Type { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 角色ID集合
|
||||
/// </summary>
|
||||
public List<long> RoleIds { get; set; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// 角色集合
|
||||
/// </summary>
|
||||
public List<AdminRoleSimpleOutput> Roles { get; set; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// 菜单树
|
||||
/// </summary>
|
||||
public List<AdminMenuOutput> Menus { get; set; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// 权限编码集合
|
||||
/// </summary>
|
||||
public List<string> PermissionCodes { get; set; } = [];
|
||||
}
|
||||
|
||||
public class AdminUserInfoOutput
|
||||
@ -37,4 +57,24 @@ public class AdminUserInfoOutput
|
||||
public string Type { get; set; } = string.Empty;
|
||||
|
||||
public int Status { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 角色ID集合
|
||||
/// </summary>
|
||||
public List<long> RoleIds { get; set; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// 角色集合
|
||||
/// </summary>
|
||||
public List<AdminRoleSimpleOutput> Roles { get; set; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// 菜单树
|
||||
/// </summary>
|
||||
public List<AdminMenuOutput> Menus { get; set; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// 权限编码集合
|
||||
/// </summary>
|
||||
public List<string> PermissionCodes { get; set; } = [];
|
||||
}
|
||||
|
||||
121
QYZH.InteractiveMagazine.Models/Dto/Ai/AiBasePromptDto.cs
Normal file
121
QYZH.InteractiveMagazine.Models/Dto/Ai/AiBasePromptDto.cs
Normal file
@ -0,0 +1,121 @@
|
||||
using QYZH.InteractiveMagazine.Models.Dto;
|
||||
|
||||
namespace QYZH.InteractiveMagazine.Models.Dto;
|
||||
|
||||
/// <summary>
|
||||
/// AIPrompt配置创建/更新输入
|
||||
/// </summary>
|
||||
public class AiBasePromptInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 配置标识(如:DEFAULT, READING, COMPREHENSION)
|
||||
/// </summary>
|
||||
public string PromptKey { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 配置名称(如:默认Prompt、阅读理解专用)
|
||||
/// </summary>
|
||||
public string PromptName { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Prompt模板内容
|
||||
/// </summary>
|
||||
public string PromptTemplate { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 配置说明
|
||||
/// </summary>
|
||||
public string? Description { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 优先级(数值越小优先级越高)
|
||||
/// </summary>
|
||||
public int Priority { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否为默认模板
|
||||
/// </summary>
|
||||
public bool IsDefault { get; set; } = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// AIPrompt配置输出
|
||||
/// </summary>
|
||||
public class AiBasePromptOutput
|
||||
{
|
||||
/// <summary>
|
||||
/// 主键ID
|
||||
/// </summary>
|
||||
public long Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 配置标识
|
||||
/// </summary>
|
||||
public string PromptKey { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 配置名称
|
||||
/// </summary>
|
||||
public string PromptName { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Prompt模板内容
|
||||
/// </summary>
|
||||
public string PromptTemplate { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 配置说明
|
||||
/// </summary>
|
||||
public string? Description { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 优先级
|
||||
/// </summary>
|
||||
public int Priority { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否为默认模板
|
||||
/// </summary>
|
||||
public bool IsDefault { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 状态(1=启用, 0=禁用)
|
||||
/// </summary>
|
||||
public int Status { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 创建人
|
||||
/// </summary>
|
||||
public string? CreatedBy { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 创建时间
|
||||
/// </summary>
|
||||
public DateTime CreatedAt { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 更新人
|
||||
/// </summary>
|
||||
public string? UpdatedBy { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 更新时间
|
||||
/// </summary>
|
||||
public DateTime? UpdatedAt { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// AIPrompt配置分页查询输入
|
||||
/// </summary>
|
||||
public class AiBasePromptQueryInput : PageQueryModel
|
||||
{
|
||||
/// <summary>
|
||||
/// 配置标识(精确匹配)
|
||||
/// </summary>
|
||||
public string? PromptKey { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 配置名称(模糊查询)
|
||||
/// </summary>
|
||||
public string? PromptName { get; set; }
|
||||
}
|
||||
23
QYZH.InteractiveMagazine.Models/Dto/Ai/AiChatDto.cs
Normal file
23
QYZH.InteractiveMagazine.Models/Dto/Ai/AiChatDto.cs
Normal file
@ -0,0 +1,23 @@
|
||||
namespace QYZH.InteractiveMagazine.Models.Dto;
|
||||
|
||||
/// <summary>
|
||||
/// AI聊天输入
|
||||
/// </summary>
|
||||
public class AiChatInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 用户需求文本
|
||||
/// </summary>
|
||||
public string Message { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// AI聊天输出
|
||||
/// </summary>
|
||||
public class AiChatOutput
|
||||
{
|
||||
/// <summary>
|
||||
/// AI返回的内容(Markdown格式提示词)
|
||||
/// </summary>
|
||||
public string Content { get; set; } = string.Empty;
|
||||
}
|
||||
163
QYZH.InteractiveMagazine.Models/Dto/Banner/BannerDto.cs
Normal file
163
QYZH.InteractiveMagazine.Models/Dto/Banner/BannerDto.cs
Normal file
@ -0,0 +1,163 @@
|
||||
using QYZH.InteractiveMagazine.Common.Helpers;
|
||||
using QYZH.InteractiveMagazine.Models.Enum;
|
||||
|
||||
namespace QYZH.InteractiveMagazine.Models.Dto.Banner;
|
||||
|
||||
/// <summary>
|
||||
/// 轮播图创建/更新输入
|
||||
/// </summary>
|
||||
public class BannerInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 标题
|
||||
/// </summary>
|
||||
public string Title { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 轮播图OSS路径
|
||||
/// </summary>
|
||||
public string ImageUrl { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 展示位置
|
||||
/// </summary>
|
||||
public BannerPositionEnum Position { get; set; } = BannerPositionEnum.Home;
|
||||
|
||||
/// <summary>
|
||||
/// 跳转类型
|
||||
/// </summary>
|
||||
public BannerTargetTypeEnum TargetType { get; set; } = BannerTargetTypeEnum.None;
|
||||
|
||||
/// <summary>
|
||||
/// 跳转值
|
||||
/// </summary>
|
||||
public string? TargetValue { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 排序值,越小越靠前
|
||||
/// </summary>
|
||||
public int Sort { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 生效开始时间
|
||||
/// </summary>
|
||||
public DateTime? StartTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 生效结束时间
|
||||
/// </summary>
|
||||
public DateTime? EndTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 备注
|
||||
/// </summary>
|
||||
public string? Remark { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 轮播图输出
|
||||
/// </summary>
|
||||
public class BannerOutput
|
||||
{
|
||||
private string _imageUrl = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 主键ID
|
||||
/// </summary>
|
||||
public long Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 标题
|
||||
/// </summary>
|
||||
public string Title { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 轮播图完整地址
|
||||
/// </summary>
|
||||
public string ImageUrl
|
||||
{
|
||||
get => DomainHelper.OssFullUrl(_imageUrl);
|
||||
set => _imageUrl = value ?? string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 展示位置
|
||||
/// </summary>
|
||||
public BannerPositionEnum Position { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 跳转类型
|
||||
/// </summary>
|
||||
public BannerTargetTypeEnum TargetType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 跳转值
|
||||
/// </summary>
|
||||
public string? TargetValue { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 排序值,越小越靠前
|
||||
/// </summary>
|
||||
public int Sort { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 生效开始时间
|
||||
/// </summary>
|
||||
public DateTime? StartTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 生效结束时间
|
||||
/// </summary>
|
||||
public DateTime? EndTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 备注
|
||||
/// </summary>
|
||||
public string? Remark { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 状态
|
||||
/// </summary>
|
||||
public int Status { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 创建人
|
||||
/// </summary>
|
||||
public string? CreatedBy { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 创建时间
|
||||
/// </summary>
|
||||
public DateTime CreatedAt { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 修改人
|
||||
/// </summary>
|
||||
public string? UpdatedBy { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 修改时间
|
||||
/// </summary>
|
||||
public DateTime? UpdatedAt { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 轮播图分页查询输入
|
||||
/// </summary>
|
||||
public class BannerQueryInput : PageQueryModel
|
||||
{
|
||||
/// <summary>
|
||||
/// 标题
|
||||
/// </summary>
|
||||
public string? Title { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 展示位置
|
||||
/// </summary>
|
||||
public BannerPositionEnum? Position { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 状态
|
||||
/// </summary>
|
||||
public int? Status { get; set; }
|
||||
}
|
||||
@ -195,5 +195,23 @@ public enum ResultCode
|
||||
FORBIDDEN = 403,
|
||||
|
||||
[Description("Bad Request")]
|
||||
BAD_REQUEST = 400
|
||||
BAD_REQUEST = 400,
|
||||
|
||||
[Description("资源不存在")]
|
||||
NOT_FOUND = 404,
|
||||
|
||||
[Description("冲突/重复提交")]
|
||||
CONFLICT = 409,
|
||||
|
||||
[Description("业务规则校验失败")]
|
||||
UNPROCESSABLE_ENTITY = 422,
|
||||
|
||||
[Description("请求过于频繁")]
|
||||
TOO_MANY_REQUESTS = 429,
|
||||
|
||||
[Description("第三方服务异常")]
|
||||
BAD_GATEWAY = 502,
|
||||
|
||||
[Description("服务不可用")]
|
||||
SERVICE_UNAVAILABLE = 503
|
||||
}
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
using QYZH.InteractiveMagazine.Models.Enum;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace QYZH.InteractiveMagazine.Models.Dto;
|
||||
|
||||
@ -10,17 +11,15 @@ public class BindJournalInput
|
||||
/// <summary>
|
||||
/// 期刊模板Id(扫码解析的期刊定义Id)
|
||||
/// </summary>
|
||||
[JsonNumberHandling(JsonNumberHandling.AllowReadingFromString)]
|
||||
public long JournalId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 实例化期刊Id(扫码解析的具体期刊实例Id,可选)
|
||||
/// </summary>
|
||||
[JsonNumberHandling(JsonNumberHandling.AllowReadingFromString)]
|
||||
public long Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 关联类型: Read(已读), Favorite(收藏), Subscribe(订阅),默认 Subscribe
|
||||
/// </summary>
|
||||
public string Type { get; set; } = UserJournalTypeEnum.Subscribe.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -59,6 +58,37 @@ public class BindJournalOutput
|
||||
public DateTime CreatedAt { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 用户绑定期刊消息
|
||||
/// </summary>
|
||||
public class BindJournalMessage
|
||||
{
|
||||
/// <summary>
|
||||
/// 用户Id
|
||||
/// </summary>
|
||||
public long UserId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 期刊Id
|
||||
/// </summary>
|
||||
public long JournalId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 期刊开始时间
|
||||
/// </summary>
|
||||
public DateTime? StartTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 期刊结束时间
|
||||
/// </summary>
|
||||
public DateTime? EndTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 用户作答信息上传地址
|
||||
/// </summary>
|
||||
public string UploadDomain { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 用户期刊关联查询输入DTO
|
||||
/// </summary>
|
||||
@ -79,3 +109,172 @@ public class UserJournalQueryInput : PageQueryModel
|
||||
/// </summary>
|
||||
public string? Type { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 生成期刊二维码输入DTO
|
||||
/// </summary>
|
||||
public class CreateUserJournalQrCodeInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 期刊Id
|
||||
/// </summary>
|
||||
public long JournalId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 生成数量
|
||||
/// </summary>
|
||||
public int Count { get; set; } = 1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 生成期刊二维码提交结果
|
||||
/// </summary>
|
||||
public class CreateUserJournalQrCodeOutput
|
||||
{
|
||||
/// <summary>
|
||||
/// 期刊Id
|
||||
/// </summary>
|
||||
public long JournalId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 请求生成数量
|
||||
/// </summary>
|
||||
public int RequestedCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 已提交生成数量
|
||||
/// </summary>
|
||||
public int AcceptedCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 二维码记录Id列表
|
||||
/// </summary>
|
||||
[JsonNumberHandling(JsonNumberHandling.WriteAsString)]
|
||||
public List<long> RecordIds { get; set; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// 是否后台生成
|
||||
/// </summary>
|
||||
public bool IsAsync { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 提示信息
|
||||
/// </summary>
|
||||
public string Message { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 期刊二维码查询输入DTO
|
||||
/// </summary>
|
||||
public class GenerateUserJournalQrCodeMessage
|
||||
{
|
||||
/// <summary>
|
||||
/// 二维码记录Id列表
|
||||
/// </summary>
|
||||
public List<long> RecordIds { get; set; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// 操作人名称
|
||||
/// </summary>
|
||||
public string OperatorName { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 期刊二维码查询输入DTO
|
||||
/// </summary>
|
||||
public class UserJournalQrCodeQueryInput : PageQueryModel
|
||||
{
|
||||
/// <summary>
|
||||
/// 期刊Id
|
||||
/// </summary>
|
||||
public long? JournalId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 用户Id
|
||||
/// </summary>
|
||||
public long? UserId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否已绑定用户
|
||||
/// </summary>
|
||||
public bool? IsBound { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 状态
|
||||
/// </summary>
|
||||
public int? Status { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 期刊二维码输出DTO
|
||||
/// </summary>
|
||||
public class UserJournalQrCodeOutput
|
||||
{
|
||||
/// <summary>
|
||||
/// 二维码记录Id
|
||||
/// </summary>
|
||||
[JsonNumberHandling(JsonNumberHandling.WriteAsString)]
|
||||
public long Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 期刊Id
|
||||
/// </summary>
|
||||
public long JournalId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 期刊名称
|
||||
/// </summary>
|
||||
public string JournalName { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 用户Id
|
||||
/// </summary>
|
||||
public long? UserId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 用户昵称
|
||||
/// </summary>
|
||||
public string? UserName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 关联类型
|
||||
/// </summary>
|
||||
public string Type { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 状态
|
||||
/// </summary>
|
||||
public string Status { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 是否已绑定用户
|
||||
/// </summary>
|
||||
public bool IsBound { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 二维码内容
|
||||
/// </summary>
|
||||
public string QrCodeUrl { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 创建时间
|
||||
/// </summary>
|
||||
public DateTime CreatedAt { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 绑定时间
|
||||
/// </summary>
|
||||
public DateTime? BoundAt { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 删除期刊二维码输入DTO
|
||||
/// </summary>
|
||||
public class DeleteUserJournalQrCodeInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 二维码记录Id列表
|
||||
/// </summary>
|
||||
[JsonNumberHandling(JsonNumberHandling.AllowReadingFromString)]
|
||||
public List<long> Ids { get; set; } = [];
|
||||
}
|
||||
|
||||
@ -21,6 +21,11 @@ namespace QYZH.InteractiveMagazine.Models.Dto.Journal
|
||||
/// </summary>
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:标题
|
||||
/// </summary>
|
||||
public string? Title { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:总页数
|
||||
/// </summary>
|
||||
@ -31,7 +36,6 @@ namespace QYZH.InteractiveMagazine.Models.Dto.Journal
|
||||
/// </summary>
|
||||
public int VerifyPage { get; set; }
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Desc:状态
|
||||
@ -105,41 +109,46 @@ namespace QYZH.InteractiveMagazine.Models.Dto.Journal
|
||||
set => _pdfPreviewUrl = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Desc:结论json
|
||||
/// </summary>
|
||||
public object? Conclusion { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:思路导读
|
||||
/// Desc:
|
||||
/// </summary>
|
||||
public string? Guide { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:家长指导
|
||||
/// </summary>
|
||||
public string? Tutelage { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:所属机构列表
|
||||
/// </summary>
|
||||
public Dictionary<long, string>? Organization { get; set; }
|
||||
|
||||
//public List<Dictionary<long, string>> Organizations => Organization.Select(c => new Dictionary<long, string>() { { c.Key, c.Value } }).ToList();
|
||||
|
||||
/// <summary>
|
||||
/// Desc:副标题
|
||||
/// </summary>
|
||||
public string? Title { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 年级
|
||||
/// </summary>
|
||||
public int Grade { get; set; }
|
||||
public string? Description { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:下载书籍页码点阵码PDF文件名称
|
||||
/// </summary>
|
||||
public string? DownloadJournalPagePdfName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 发布开始时间
|
||||
/// </summary>
|
||||
public DateTime? StartTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 发布结束时间
|
||||
/// </summary>
|
||||
public DateTime? EndTime { get; set; }
|
||||
|
||||
|
||||
public string CreatedBy { get; set; }
|
||||
public DateTime CreatedAt { get; set; }
|
||||
}
|
||||
|
||||
public class JournalPublishBookMessage
|
||||
{
|
||||
public long BookId { get; set; }
|
||||
public DateTime StartTime { get; set; }
|
||||
public DateTime EndTime { get; set; }
|
||||
}
|
||||
|
||||
public class JournalPublishBookPageMessage
|
||||
{
|
||||
public long BookId { get; set; }
|
||||
public long PageId { get; set; }
|
||||
public string Url { get; set; }
|
||||
public string? PageNo { get; set; }
|
||||
public string? Layout { get; set; }
|
||||
public string QuestionNo { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,28 +8,24 @@ using System.Threading.Tasks;
|
||||
|
||||
namespace QYZH.InteractiveMagazine.Models.Dto.Journal
|
||||
{
|
||||
public partial class JournalEditDto
|
||||
public partial class JournalAddDto
|
||||
{
|
||||
/// <summary>
|
||||
/// Desc:主键id
|
||||
/// </summary>
|
||||
public long Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:书籍名称
|
||||
/// </summary>
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:标题
|
||||
/// </summary>
|
||||
public string? Title { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:总页数
|
||||
/// </summary>
|
||||
public int TotalPage { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:校验页数
|
||||
/// </summary>
|
||||
public int VerifyPage { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Desc:状态
|
||||
@ -57,10 +53,6 @@ namespace QYZH.InteractiveMagazine.Models.Dto.Journal
|
||||
[JsonIgnore]
|
||||
public int Revision { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:审查结果(默认0 1通过 -1驳回)
|
||||
/// </summary>
|
||||
public int Result { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:封面
|
||||
@ -75,42 +67,30 @@ namespace QYZH.InteractiveMagazine.Models.Dto.Journal
|
||||
/// <summary>
|
||||
/// Desc:pdf预览地址
|
||||
/// </summary>
|
||||
public string PdfPreviewUrl { get; set; }
|
||||
public string? PdfPreviewUrl { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:结论json
|
||||
/// Desc:发布开始时间
|
||||
/// </summary>
|
||||
public object Conclusion { get; set; }
|
||||
public DateTime? StartTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:思路导读
|
||||
/// Desc:发布结束时间
|
||||
/// </summary>
|
||||
public string Guide { get; set; }
|
||||
public DateTime? EndTime { get; set; }
|
||||
|
||||
}
|
||||
public partial class JournalEditDto: JournalAddDto
|
||||
{
|
||||
/// <summary>
|
||||
/// Desc:主键id
|
||||
/// </summary>
|
||||
public long Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:家长指导
|
||||
/// Desc:状态
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public string Tutelage { get; set; }
|
||||
public JournalStatusEnum Status { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:所属机构
|
||||
/// </summary>
|
||||
public long[] OrganizationIds { get; set; }
|
||||
|
||||
///// <summary>
|
||||
///// Desc:所属机构
|
||||
///// </summary>
|
||||
//public string OrganizationName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:副标题
|
||||
/// </summary>
|
||||
public string Title { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 年级
|
||||
/// </summary>
|
||||
public int Grade { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,42 @@
|
||||
using QYZH.InteractiveMagazine.Models.Enum;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace QYZH.InteractiveMagazine.Models.Dto.Journal
|
||||
{
|
||||
public class JournalPagePrintDto
|
||||
{
|
||||
/// <summary>
|
||||
/// 书Id
|
||||
/// </summary>
|
||||
public long JournalId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 状态
|
||||
/// </summary>
|
||||
public JournalStatusEnum? Status { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 书PDF URL(这里带有 OSS 域名 https://oss.qyzhjy.com/)
|
||||
/// </summary>
|
||||
public string? JournalPdfUrl { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 页码数组
|
||||
/// </summary>
|
||||
public int[] PageNum { get; set; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// 点阵码打印页面数组
|
||||
/// </summary>
|
||||
public string[] PageNo { get; set; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// 下载书页PDF的文件名,铺码程序已经添加好(这里带有 OSS 域名 https://oss.qyzhjy.com/)
|
||||
/// </summary>
|
||||
public string? DownloadJournalPagePdfName { get; set; }
|
||||
}
|
||||
}
|
||||
@ -30,33 +30,9 @@ namespace QYZH.InteractiveMagazine.Models.Dto.Journal
|
||||
public TaskBankTypeEnum Type { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 学科Id
|
||||
/// 是否需要AI批改,默认需要
|
||||
/// </summary>
|
||||
public long SubjectId { get; set; }
|
||||
public bool NeedAiProcess { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// 分数
|
||||
/// </summary>
|
||||
public float Score { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 回答时间(秒)
|
||||
/// </summary>
|
||||
public int AnswerTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 关联的知识点ids
|
||||
/// </summary>
|
||||
public string KnowledgePointIds { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 关键解析
|
||||
/// </summary>
|
||||
public string KeywordAnalysis { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 问题内容
|
||||
/// </summary>
|
||||
public string Task { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
using QYZH.InteractiveMagazine.Models.Enum;
|
||||
using QYZH.InteractiveMagazine.Models.Enum;
|
||||
using QYZH.InteractiveMagazine.Common.Extensions;
|
||||
using QYZH.InteractiveMagazine.Common.Helpers;
|
||||
using QYZH.InteractiveMagazine.Models.Enum;
|
||||
@ -18,12 +18,10 @@ namespace QYZH.InteractiveMagazine.Models.Dto.Journal
|
||||
/// 书页Id
|
||||
/// </summary>
|
||||
public long JournalPageId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 学科Id
|
||||
/// 任务信息
|
||||
/// </summary>
|
||||
public long SubjectId { get; set; }
|
||||
|
||||
public string Task { get; set; }
|
||||
/// <summary>
|
||||
/// 题号
|
||||
/// </summary>
|
||||
@ -36,24 +34,66 @@ namespace QYZH.InteractiveMagazine.Models.Dto.Journal
|
||||
|
||||
public string ShowType => Type.GetDescription();
|
||||
|
||||
private string _task;
|
||||
/// <summary>
|
||||
/// 问题
|
||||
/// 答案列表
|
||||
/// </summary>
|
||||
public string Task
|
||||
{
|
||||
get { return _task.AddDomain(DomainHelper.OssDomain); }
|
||||
set { _task = value; }
|
||||
}
|
||||
public List<JournalTaskAnswerOutput> Answers { get; set; }
|
||||
/// <summary>
|
||||
/// 积分
|
||||
/// </summary>
|
||||
public float Points { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 选项
|
||||
/// 成长值
|
||||
/// </summary>
|
||||
public string Options { get; set; }
|
||||
public float GrowthPoint { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 理解力评分
|
||||
/// </summary>
|
||||
public float Comprehension { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 判断力评分
|
||||
/// </summary>
|
||||
public float Judgment { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 表达力评分
|
||||
/// </summary>
|
||||
public float Expression { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 说服力评分
|
||||
/// </summary>
|
||||
public float Persuasiveness { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 作答参考时间(秒)
|
||||
/// </summary>
|
||||
public int AnswerTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Prompt配置
|
||||
/// </summary>
|
||||
public string Prompt { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否需要AI批改
|
||||
/// </summary>
|
||||
public bool NeedAiProcess { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 答案输出
|
||||
/// </summary>
|
||||
public class JournalTaskAnswerOutput
|
||||
{
|
||||
public long Id { get; set; }
|
||||
|
||||
private string _answer;
|
||||
/// <summary>
|
||||
/// 答案
|
||||
/// 参考答案
|
||||
/// </summary>
|
||||
public string Answer
|
||||
{
|
||||
@ -61,98 +101,14 @@ namespace QYZH.InteractiveMagazine.Models.Dto.Journal
|
||||
set { _answer = value; }
|
||||
}
|
||||
|
||||
private string _analysis;
|
||||
private string _answerUrl;
|
||||
/// <summary>
|
||||
/// 解析
|
||||
/// 参考答案图片
|
||||
/// </summary>
|
||||
public string Analysis
|
||||
public string AnswerUrl
|
||||
{
|
||||
get { return _analysis.AddDomain(DomainHelper.OssDomain); }
|
||||
set { _analysis = value; }
|
||||
get { return _answerUrl.AddDomain(DomainHelper.OssDomain); }
|
||||
set { _answerUrl = value; }
|
||||
}
|
||||
|
||||
public string AnalysisUrl { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 问题分数
|
||||
/// </summary>
|
||||
public float TaskScore { get; set; }
|
||||
|
||||
private string _audioUrl;
|
||||
/// <summary>
|
||||
/// 音频地址
|
||||
/// </summary>
|
||||
public string AudioUrl
|
||||
{
|
||||
get { return DomainHelper.OssFullUrl(_audioUrl); }
|
||||
set { _audioUrl = value; }
|
||||
}
|
||||
|
||||
private string _videoUrl;
|
||||
/// <summary>
|
||||
/// 视频地址
|
||||
/// </summary>
|
||||
public string VideoUrl
|
||||
{
|
||||
get { return DomainHelper.OssFullUrl(_videoUrl); }
|
||||
set { _videoUrl = value; }
|
||||
}
|
||||
|
||||
private string _pointsUrl;
|
||||
/// <summary>
|
||||
/// 点位数据地址
|
||||
/// </summary>
|
||||
public string PointsUrl
|
||||
{
|
||||
get { return DomainHelper.OssFullUrl(_pointsUrl); }
|
||||
set { _pointsUrl = value; }
|
||||
}
|
||||
|
||||
private string _imageUrl;
|
||||
/// <summary>
|
||||
/// 图片地址
|
||||
/// </summary>
|
||||
public string ImageUrl
|
||||
{
|
||||
get { return DomainHelper.OssFullUrl(_imageUrl); }
|
||||
set { _imageUrl = value; }
|
||||
}
|
||||
|
||||
public List<string> ImageUrls { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 问题分数
|
||||
/// </summary>
|
||||
public float Score { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 回答时间(秒)
|
||||
/// </summary>
|
||||
public int AnswerTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 音频开始时间
|
||||
/// </summary>
|
||||
public long AudioStartTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 音频结束时间
|
||||
/// </summary>
|
||||
public long AudioEndTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 知识点
|
||||
/// </summary>
|
||||
public string KnowledgePointIds { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 回答项解析
|
||||
/// </summary>
|
||||
public string KeywordAnalysis { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 年级
|
||||
/// </summary>
|
||||
public int Grade { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
using QYZH.InteractiveMagazine.Models.Enum;
|
||||
using QYZH.InteractiveMagazine.Models.Enum;
|
||||
using QYZH.InteractiveMagazine.Models.Enum;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@ -13,11 +13,10 @@ namespace QYZH.InteractiveMagazine.Models.Dto.Journal
|
||||
public long Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 书Id
|
||||
/// 杂志Id
|
||||
/// </summary>
|
||||
public long JournalId { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 书页id
|
||||
/// </summary>
|
||||
@ -39,59 +38,44 @@ namespace QYZH.InteractiveMagazine.Models.Dto.Journal
|
||||
public string Task { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 选项
|
||||
/// 积分
|
||||
/// </summary>
|
||||
public string Options { get; set; }
|
||||
|
||||
public int Points { get; set; }
|
||||
/// <summary>
|
||||
/// 答案
|
||||
/// 成长值
|
||||
/// </summary>
|
||||
public string Answer { get; set; }
|
||||
|
||||
public int GrowthPoint { get; set; }
|
||||
/// <summary>
|
||||
/// 答案图片
|
||||
/// 理解力评分
|
||||
/// </summary>
|
||||
public string AnswerUrl { get; set; }
|
||||
|
||||
public float Comprehension { get; set; }
|
||||
/// <summary>
|
||||
/// 解析
|
||||
/// 判断力评分
|
||||
/// </summary>
|
||||
public string Analysis { get; set; }
|
||||
|
||||
public float Judgment { get; set; }
|
||||
/// <summary>
|
||||
/// 解析图片地址
|
||||
/// 表达力评分
|
||||
/// </summary>
|
||||
public string AnalysisUrl { get; set; }
|
||||
|
||||
public float Expression { get; set; }
|
||||
/// <summary>
|
||||
/// 问题分数
|
||||
/// 说服力评分
|
||||
/// </summary>
|
||||
public float Score { get; set; }
|
||||
public float Persuasiveness { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 回答时间秒
|
||||
/// </summary>
|
||||
public int AnswerTime { get; set; }
|
||||
/// <summary>
|
||||
/// Prompt配置
|
||||
/// </summary>
|
||||
public string Prompt { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 图片
|
||||
/// 是否需要AI批改,false时由人工批改
|
||||
/// </summary>
|
||||
public string ImageUrl { get; set; }
|
||||
public bool NeedAiProcess { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// 视频
|
||||
/// </summary>
|
||||
public string VideoUrl { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 学科Id
|
||||
/// </summary>
|
||||
public long SubjectId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 关联的知识点ids
|
||||
/// </summary>
|
||||
public string KnowledgepointIds { get; set; }
|
||||
}
|
||||
|
||||
public class JournalPageTaskComplementInput
|
||||
|
||||
@ -61,19 +61,9 @@ namespace QYZH.InteractiveMagazine.Models.Dto.Journal
|
||||
public string ShowType => Type.GetDescription();
|
||||
|
||||
/// <summary>
|
||||
/// 选项
|
||||
/// 答案列表
|
||||
/// </summary>
|
||||
public string Options { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 答案
|
||||
/// </summary>
|
||||
public string Answers { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 解析
|
||||
/// </summary>
|
||||
public string Analysis { get; set; }
|
||||
public List<JournalTaskAnswerOutput> Answers { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否分配
|
||||
|
||||
@ -0,0 +1,26 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace QYZH.InteractiveMagazine.Models.Dto.Journal
|
||||
{
|
||||
public class JournalTaskAnswerAddInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 题目Id
|
||||
/// </summary>
|
||||
public long JournalPageTaskId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 参考答案
|
||||
/// </summary>
|
||||
public string Answer { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 参考答案图片
|
||||
/// </summary>
|
||||
public string AnswerUrl { get; set; }
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,20 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace QYZH.InteractiveMagazine.Models.Dto.Journal
|
||||
{
|
||||
public class JournalTaskAnswerInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 参考答案
|
||||
/// </summary>
|
||||
public string Answer { get; set; }
|
||||
/// <summary>
|
||||
/// 参考答案图片
|
||||
/// </summary>
|
||||
public string AnswerUrl { get; set; }
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,26 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace QYZH.InteractiveMagazine.Models.Dto.Journal
|
||||
{
|
||||
public class JournalTaskAnswerUpdateInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 答案Id
|
||||
/// </summary>
|
||||
public long Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 参考答案
|
||||
/// </summary>
|
||||
public string Answer { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 参考答案图片
|
||||
/// </summary>
|
||||
public string AnswerUrl { get; set; }
|
||||
}
|
||||
}
|
||||
200
QYZH.InteractiveMagazine.Models/Dto/Material/MaterialDto.cs
Normal file
200
QYZH.InteractiveMagazine.Models/Dto/Material/MaterialDto.cs
Normal file
@ -0,0 +1,200 @@
|
||||
using QYZH.InteractiveMagazine.Common.Helpers;
|
||||
using QYZH.InteractiveMagazine.Models.Enum;
|
||||
|
||||
namespace QYZH.InteractiveMagazine.Models.Dto.Material;
|
||||
|
||||
/// <summary>
|
||||
/// 资料创建/更新输入
|
||||
/// </summary>
|
||||
public class MaterialInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 资料标题
|
||||
/// </summary>
|
||||
public string Title { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 资料名称
|
||||
/// </summary>
|
||||
public string? Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 资料描述
|
||||
/// </summary>
|
||||
public string? Description { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 排序值,越小越靠前
|
||||
/// </summary>
|
||||
public int Sort { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 生效开始时间
|
||||
/// </summary>
|
||||
public DateTime? StartTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 生效结束时间
|
||||
/// </summary>
|
||||
public DateTime? EndTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 资料文件列表
|
||||
/// </summary>
|
||||
public List<MaterialFileInput> Files { get; set; } = new();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 资料文件输入
|
||||
/// </summary>
|
||||
public class MaterialFileInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 文件类型
|
||||
/// </summary>
|
||||
public MaterialFileTypeEnum FileType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 文件OSS路径
|
||||
/// </summary>
|
||||
public string FileUrl { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 文件名称
|
||||
/// </summary>
|
||||
public string? FileName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 排序值,越小越靠前
|
||||
/// </summary>
|
||||
public int Sort { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 资料输出
|
||||
/// </summary>
|
||||
public class MaterialOutput
|
||||
{
|
||||
/// <summary>
|
||||
/// 主键ID
|
||||
/// </summary>
|
||||
public long Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 资料标题
|
||||
/// </summary>
|
||||
public string Title { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 资料名称
|
||||
/// </summary>
|
||||
public string? Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 资料描述
|
||||
/// </summary>
|
||||
public string? Description { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 排序值,越小越靠前
|
||||
/// </summary>
|
||||
public int Sort { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 生效开始时间
|
||||
/// </summary>
|
||||
public DateTime? StartTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 生效结束时间
|
||||
/// </summary>
|
||||
public DateTime? EndTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 状态
|
||||
/// </summary>
|
||||
public int Status { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 创建人
|
||||
/// </summary>
|
||||
public string? CreatedBy { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 创建时间
|
||||
/// </summary>
|
||||
public DateTime CreatedAt { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 修改人
|
||||
/// </summary>
|
||||
public string? UpdatedBy { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 修改时间
|
||||
/// </summary>
|
||||
public DateTime? UpdatedAt { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 资料文件列表
|
||||
/// </summary>
|
||||
public List<MaterialFileOutput> Files { get; set; } = new();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 资料文件输出
|
||||
/// </summary>
|
||||
public class MaterialFileOutput
|
||||
{
|
||||
private string _fileUrl = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 主键ID
|
||||
/// </summary>
|
||||
public long Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 文件类型
|
||||
/// </summary>
|
||||
public MaterialFileTypeEnum FileType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 文件完整地址
|
||||
/// </summary>
|
||||
public string FileUrl
|
||||
{
|
||||
get => DomainHelper.OssFullUrl(_fileUrl);
|
||||
set => _fileUrl = value ?? string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 文件名称
|
||||
/// </summary>
|
||||
public string? FileName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 排序值,越小越靠前
|
||||
/// </summary>
|
||||
public int Sort { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 资料分页查询输入
|
||||
/// </summary>
|
||||
public class MaterialQueryInput : PageQueryModel
|
||||
{
|
||||
/// <summary>
|
||||
/// 标题
|
||||
/// </summary>
|
||||
public string? Title { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 名称
|
||||
/// </summary>
|
||||
public string? Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 状态
|
||||
/// </summary>
|
||||
public int? Status { get; set; }
|
||||
}
|
||||
@ -5,6 +5,36 @@ namespace QYZH.InteractiveMagazine.Models.Dto;
|
||||
/// </summary>
|
||||
public static class OperationLogActionType
|
||||
{
|
||||
/// <summary>
|
||||
/// 新增
|
||||
/// </summary>
|
||||
public const string Create = "Create";
|
||||
|
||||
/// <summary>
|
||||
/// 修改
|
||||
/// </summary>
|
||||
public const string Update = "Update";
|
||||
|
||||
/// <summary>
|
||||
/// 删除
|
||||
/// </summary>
|
||||
public const string Delete = "Delete";
|
||||
|
||||
/// <summary>
|
||||
/// 状态变更
|
||||
/// </summary>
|
||||
public const string StatusChange = "StatusChange";
|
||||
|
||||
/// <summary>
|
||||
/// 分配
|
||||
/// </summary>
|
||||
public const string Assign = "Assign";
|
||||
|
||||
/// <summary>
|
||||
/// 修改密码
|
||||
/// </summary>
|
||||
public const string ChangePassword = "ChangePassword";
|
||||
|
||||
/// <summary>
|
||||
/// 手动增加积分
|
||||
/// </summary>
|
||||
@ -31,17 +61,163 @@ public static class OperationLogActionType
|
||||
/// </summary>
|
||||
public static class OperationLogTargetType
|
||||
{
|
||||
/// <summary>
|
||||
/// 管理员
|
||||
/// </summary>
|
||||
public const string AdminUser = "AdminUser";
|
||||
|
||||
/// <summary>
|
||||
/// 权限菜单
|
||||
/// </summary>
|
||||
public const string AdminMenu = "AdminMenu";
|
||||
|
||||
/// <summary>
|
||||
/// 管理员角色
|
||||
/// </summary>
|
||||
public const string AdminRole = "AdminRole";
|
||||
|
||||
/// <summary>
|
||||
/// 用户
|
||||
/// </summary>
|
||||
public const string User = "User";
|
||||
|
||||
/// <summary>
|
||||
/// 社区留言
|
||||
/// </summary>
|
||||
public const string CommunityMessage = "CommunityMessage";
|
||||
|
||||
/// <summary>
|
||||
/// 勋章
|
||||
/// </summary>
|
||||
public const string Medal = "Medal";
|
||||
|
||||
/// <summary>
|
||||
/// AI 基础提示词
|
||||
/// </summary>
|
||||
public const string AiBasePrompt = "AiBasePrompt";
|
||||
|
||||
/// <summary>
|
||||
/// 签到配置
|
||||
/// </summary>
|
||||
public const string CheckInConfig = "CheckInConfig";
|
||||
|
||||
/// <summary>
|
||||
/// Banner
|
||||
/// </summary>
|
||||
public const string Banner = "Banner";
|
||||
|
||||
/// <summary>
|
||||
/// 素材
|
||||
/// </summary>
|
||||
public const string Material = "Material";
|
||||
|
||||
/// <summary>
|
||||
/// 商品
|
||||
/// </summary>
|
||||
public const string Product = "Product";
|
||||
|
||||
/// <summary>
|
||||
/// 宠物
|
||||
/// </summary>
|
||||
public const string Pet = "Pet";
|
||||
|
||||
/// <summary>
|
||||
/// 宠物进化
|
||||
/// </summary>
|
||||
public const string PetEvolution = "PetEvolution";
|
||||
|
||||
/// <summary>
|
||||
/// 宠物皮肤
|
||||
/// </summary>
|
||||
public const string PetSkin = "PetSkin";
|
||||
|
||||
/// <summary>
|
||||
/// 宠物皮肤图片
|
||||
/// </summary>
|
||||
public const string PetSkinImage = "PetSkinImage";
|
||||
|
||||
/// <summary>
|
||||
/// 用户期刊二维码
|
||||
/// </summary>
|
||||
public const string UserJournalQrCode = "UserJournalQrCode";
|
||||
|
||||
/// <summary>
|
||||
/// 期刊
|
||||
/// </summary>
|
||||
public const string Journal = "Journal";
|
||||
|
||||
/// <summary>
|
||||
/// 期刊目录
|
||||
/// </summary>
|
||||
public const string JournalCatalog = "JournalCatalog";
|
||||
|
||||
/// <summary>
|
||||
/// 期刊书页
|
||||
/// </summary>
|
||||
public const string JournalPage = "JournalPage";
|
||||
|
||||
/// <summary>
|
||||
/// 期刊书页任务
|
||||
/// </summary>
|
||||
public const string JournalPageTask = "JournalPageTask";
|
||||
|
||||
/// <summary>
|
||||
/// 期刊任务答案
|
||||
/// </summary>
|
||||
public const string JournalTaskAnswer = "JournalTaskAnswer";
|
||||
|
||||
/// <summary>
|
||||
/// 补偿任务
|
||||
/// </summary>
|
||||
public const string CompensationTask = "CompensationTask";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 操作日志记录输入
|
||||
/// </summary>
|
||||
public class OperationLogRecordInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 操作人Id
|
||||
/// </summary>
|
||||
public long OperatorId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 操作人用户名
|
||||
/// </summary>
|
||||
public string OperatorName { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 操作类型
|
||||
/// </summary>
|
||||
public string ActionType { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 目标类型
|
||||
/// </summary>
|
||||
public string TargetType { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 目标记录Id
|
||||
/// </summary>
|
||||
public long TargetId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 目标名称
|
||||
/// </summary>
|
||||
public string? TargetName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 操作详情 JSON
|
||||
/// </summary>
|
||||
public string? Detail { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// IP地址
|
||||
/// </summary>
|
||||
public string? IpAddress { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 操作日志分页查询输入
|
||||
/// </summary>
|
||||
|
||||
@ -0,0 +1,38 @@
|
||||
namespace QYZH.InteractiveMagazine.Models.Dto.RabbitMQ;
|
||||
|
||||
/// <summary>
|
||||
/// MQ消息发布入参。
|
||||
/// </summary>
|
||||
/// <typeparam name="T">消息数据类型。</typeparam>
|
||||
public class MessagePublishInput<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// 交换机。
|
||||
/// </summary>
|
||||
public string Exchange { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 队列。
|
||||
/// </summary>
|
||||
public string Queue { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 路由键。
|
||||
/// </summary>
|
||||
public string RoutingKey { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 消息数据。
|
||||
/// </summary>
|
||||
public T Data { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 业务类型。
|
||||
/// </summary>
|
||||
public string BusinessType { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 业务ID。
|
||||
/// </summary>
|
||||
public long BusinessId { get; set; }
|
||||
}
|
||||
@ -0,0 +1,22 @@
|
||||
namespace QYZH.InteractiveMagazine.Models.Dto.RabbitMQ;
|
||||
|
||||
/// <summary>
|
||||
/// MQ消息发布结果。
|
||||
/// </summary>
|
||||
public class MessagePublishResult
|
||||
{
|
||||
/// <summary>
|
||||
/// 是否成功。
|
||||
/// </summary>
|
||||
public bool Success { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Outbox消息ID。
|
||||
/// </summary>
|
||||
public List<long> OutboxIds { get; set; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// 结果消息。
|
||||
/// </summary>
|
||||
public string Message { get; set; } = string.Empty;
|
||||
}
|
||||
@ -0,0 +1,85 @@
|
||||
namespace QYZH.InteractiveMagazine.Models.Dto.UserAnswerTaskService;
|
||||
|
||||
/// <summary>
|
||||
/// 批量领取任务积分输入
|
||||
/// </summary>
|
||||
public class BatchClaimPointsInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 任务分组Id列表(JournalPageTaskGroupId)
|
||||
/// </summary>
|
||||
public HashSet<long> GroupIds { get; set; } = [];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 批量领取任务积分输出
|
||||
/// </summary>
|
||||
public class BatchClaimPointsOutput
|
||||
{
|
||||
/// <summary>
|
||||
/// 成功领取数量
|
||||
/// </summary>
|
||||
public int SuccessCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 失败数量
|
||||
/// </summary>
|
||||
public int FailCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 成功领取详情列表
|
||||
/// </summary>
|
||||
public List<BatchClaimSuccessItem> SuccessItems { get; set; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// 失败详情
|
||||
/// </summary>
|
||||
public List<BatchClaimFailItem> FailItems { get; set; } = [];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 批量领取成功项
|
||||
/// </summary>
|
||||
public class BatchClaimSuccessItem
|
||||
{
|
||||
/// <summary>
|
||||
/// 任务编号(从 JournalPageTask.No 字段按 - 分割取索引2,前面加"任务")
|
||||
/// </summary>
|
||||
public string TaskNo { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 积分值
|
||||
/// </summary>
|
||||
public int Points { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 成长值
|
||||
/// </summary>
|
||||
public int GrowthPoints { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 题目得分
|
||||
/// </summary>
|
||||
public float Score { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 批量领取失败项
|
||||
/// </summary>
|
||||
public class BatchClaimFailItem
|
||||
{
|
||||
/// <summary>
|
||||
/// 任务分组Id
|
||||
/// </summary>
|
||||
public long GroupId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 任务编号(从 JournalPageTask.No 字段按 - 分割取索引2,前面加"任务")
|
||||
/// </summary>
|
||||
public string TaskNo { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 失败原因
|
||||
/// </summary>
|
||||
public string Message { get; set; }
|
||||
}
|
||||
@ -0,0 +1,32 @@
|
||||
namespace QYZH.InteractiveMagazine.Models.Dto.UserAnswerTaskService;
|
||||
|
||||
/// <summary>
|
||||
/// 目录列表输出(按一级目录分组)
|
||||
/// </summary>
|
||||
public class CatalogListOutput
|
||||
{
|
||||
/// <summary>
|
||||
/// 一级目录ID
|
||||
/// </summary>
|
||||
public long CatalogId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 一级目录名称
|
||||
/// </summary>
|
||||
public string CatalogName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 杂志名称
|
||||
/// </summary>
|
||||
public string JournalName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 文章篇数
|
||||
/// </summary>
|
||||
public int ArticleCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 二级栏目列表
|
||||
/// </summary>
|
||||
public List<SecondCatalogOutput> Children { get; set; } = [];
|
||||
}
|
||||
@ -0,0 +1,32 @@
|
||||
namespace QYZH.InteractiveMagazine.Models.Dto.UserAnswerTaskService;
|
||||
|
||||
/// <summary>
|
||||
/// 一级目录概览输出(含二级目录详情列表)
|
||||
/// </summary>
|
||||
public class CatalogOverviewOutput
|
||||
{
|
||||
/// <summary>
|
||||
/// 一级目录ID
|
||||
/// </summary>
|
||||
public long CatalogId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 一级目录名称
|
||||
/// </summary>
|
||||
public string FirstLevelCatalogName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 文章篇数(该一级目录下 JournalPage 的个数)
|
||||
/// </summary>
|
||||
public int PageCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 总任务数(该一级目录下所有 JournalPageTask 的条数)
|
||||
/// </summary>
|
||||
public int TotalTaskCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 二级目录详情列表
|
||||
/// </summary>
|
||||
public List<SubCatalogDetailOutput> SubCatalogs { get; set; } = new();
|
||||
}
|
||||
@ -0,0 +1,37 @@
|
||||
namespace QYZH.InteractiveMagazine.Models.Dto.UserAnswerTaskService;
|
||||
|
||||
/// <summary>
|
||||
/// 目录任务详情输出(含任务列表和徽章数)
|
||||
/// </summary>
|
||||
public class CatalogTaskDetailOutput
|
||||
{
|
||||
/// <summary>
|
||||
/// 目录ID
|
||||
/// </summary>
|
||||
public long CatalogId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 目录名称
|
||||
/// </summary>
|
||||
public string CatalogName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 杂志起始页码
|
||||
/// </summary>
|
||||
public int StartPage { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 杂志结束页码
|
||||
/// </summary>
|
||||
public int EndPage { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 徽章个数
|
||||
/// </summary>
|
||||
public int MedalCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 任务列表
|
||||
/// </summary>
|
||||
public List<TaskStatusOutput> Tasks { get; set; } = new();
|
||||
}
|
||||
@ -0,0 +1,130 @@
|
||||
namespace QYZH.InteractiveMagazine.Models.Dto.UserAnswerTaskService;
|
||||
|
||||
/// <summary>
|
||||
/// 目录树形结构输出(包含一级目录列表)
|
||||
/// </summary>
|
||||
public class CatalogTreeOutput
|
||||
{
|
||||
/// <summary>
|
||||
/// 一级目录列表
|
||||
/// </summary>
|
||||
public List<FirstLevelCatalogItem> FirstLevelCatalogs { get; set; } = [];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 一级目录项输出
|
||||
/// </summary>
|
||||
public class FirstLevelCatalogItem
|
||||
{
|
||||
/// <summary>
|
||||
/// 一级目录ID
|
||||
/// </summary>
|
||||
public long CatalogId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 一级目录名称
|
||||
/// </summary>
|
||||
public string CatalogName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 文章篇数(来自二级目录对应的所有有题目的页面数总和)
|
||||
/// </summary>
|
||||
public int ArticleCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 任务数量(来自二级目录对应的所有任务按GroupId去重后的总和)
|
||||
/// </summary>
|
||||
public int TaskCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 二级目录列表
|
||||
/// </summary>
|
||||
public List<SecondLevelCatalogItem> Children { get; set; } = [];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 二级目录项输出(含页码范围、任务数量、状态、总成长值、总积分及任务列表)
|
||||
/// </summary>
|
||||
public class SecondLevelCatalogItem
|
||||
{
|
||||
/// <summary>
|
||||
/// 二级目录ID
|
||||
/// </summary>
|
||||
public long CatalogId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 二级目录名称
|
||||
/// </summary>
|
||||
public string CatalogName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 起始页码
|
||||
/// </summary>
|
||||
public int StartPage { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 结束页码
|
||||
/// </summary>
|
||||
public int EndPage { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 任务数量(按GroupId去重后的数量)
|
||||
/// </summary>
|
||||
public int TaskCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 状态(格式如:1/2 进行中、0/2 未开始、2/2 已完成)
|
||||
/// </summary>
|
||||
public string Status { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 总成长值(该二级目录下所有任务的成长值总和,按GroupId去重)
|
||||
/// </summary>
|
||||
public int TotalGrowthPoints { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 总积分(该二级目录下所有任务的积分总和,按GroupId去重)
|
||||
/// </summary>
|
||||
public int TotalPoints { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 任务列表(每个任务的详细信息)
|
||||
/// </summary>
|
||||
public List<CatalogTaskItem> Tasks { get; set; } = [];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 目录任务项输出(任务详情)
|
||||
/// </summary>
|
||||
public class CatalogTaskItem
|
||||
{
|
||||
/// <summary>
|
||||
/// 任务分组ID(GroupId)
|
||||
/// </summary>
|
||||
public long GroupId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 任务名称(取主任务的Task字段)
|
||||
/// </summary>
|
||||
public string TaskName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 任务状态(未开始、进行中、已完成)
|
||||
/// </summary>
|
||||
public string Status { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 成长值(取主任务的GrowthPoint)
|
||||
/// </summary>
|
||||
public int GrowthPoints { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 积分值(取主任务的Points)
|
||||
/// </summary>
|
||||
public int Points { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 答题时长(分钟,该GroupId下所有任务的AnswerSeconds累加后转换为分钟)
|
||||
/// </summary>
|
||||
public double AnswerMinutes { get; set; }
|
||||
}
|
||||
@ -0,0 +1,27 @@
|
||||
namespace QYZH.InteractiveMagazine.Models.Dto.UserAnswerTaskService;
|
||||
|
||||
/// <summary>
|
||||
/// 领取任务积分输出
|
||||
/// </summary>
|
||||
public class ClaimTaskPointsOutput
|
||||
{
|
||||
/// <summary>
|
||||
/// 总成长值(主任务的 GrowthPoint)
|
||||
/// </summary>
|
||||
public int TotalGrowthPoints { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 总积分(主任务的 Points)
|
||||
/// </summary>
|
||||
public int TotalPoints { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 得分(从 JournalPageTaskUserAnswer 表获取,JournalPageTaskId == GroupId 的 Score)
|
||||
/// </summary>
|
||||
public float Score { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 任务编号(从 JournalPageTask.No 字段按 - 分割取索引2,前面加"任务")
|
||||
/// </summary>
|
||||
public string TaskNo { get; set; }
|
||||
}
|
||||
@ -0,0 +1,42 @@
|
||||
namespace QYZH.InteractiveMagazine.Models.Dto.UserAnswerTaskService;
|
||||
|
||||
/// <summary>
|
||||
/// 跨页题目详情输出
|
||||
/// </summary>
|
||||
public class CrossPageTaskOutput
|
||||
{
|
||||
/// <summary>
|
||||
/// 一级栏目名称
|
||||
/// </summary>
|
||||
public string FirstLevelCatalogName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 栏目名称(二级栏目/文章名称)
|
||||
/// </summary>
|
||||
public string CatalogName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 任务名称
|
||||
/// </summary>
|
||||
public string TaskName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 图片数组(已答题用PageAnswerUrl,未答题用TaskUrl)
|
||||
/// </summary>
|
||||
public List<string> Images { get; set; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// 页面作答点读图片地址集合(已答题返回,未答题返回空集合)
|
||||
/// </summary>
|
||||
public List<string> PageAnswerDotUrl { get; set; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// 是否跨页题目
|
||||
/// </summary>
|
||||
public bool IsCrossPage { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否可以领取积分(任务已完成且未领取过积分)
|
||||
/// </summary>
|
||||
public bool CanClaimPoints { get; set; }
|
||||
}
|
||||
@ -0,0 +1,57 @@
|
||||
namespace QYZH.InteractiveMagazine.Models.Dto.UserAnswerTaskService;
|
||||
|
||||
/// <summary>
|
||||
/// 用户期刊学习进度输出(按期刊分组)
|
||||
/// </summary>
|
||||
public class JournalProgressOutput
|
||||
{
|
||||
/// <summary>
|
||||
/// 期刊ID
|
||||
/// </summary>
|
||||
public long JournalId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 期刊名称
|
||||
/// </summary>
|
||||
public string JournalName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 栏目数(一级目录个数)
|
||||
/// </summary>
|
||||
public int CatalogCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 任务总数(该期刊下 JournalPageTask 的条数)
|
||||
/// </summary>
|
||||
public int TotalTaskCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 已完成任务数(按 JournalPageTaskGroupId 去重,同一题跨页算1个)
|
||||
/// </summary>
|
||||
public int CompletedTaskCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 成长值(已完成任务的 GrowthPoint 累加)
|
||||
/// </summary>
|
||||
public int TotalGrowthPoints { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 积分值(已完成任务的 Points 累加)
|
||||
/// </summary>
|
||||
public int TotalPoints { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 进度百分比(已完成任务ID数 / 总任务数 × 100)
|
||||
/// </summary>
|
||||
public double Progress { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 剩余篇数(该期刊总页数 - 用户已作答页数)
|
||||
/// </summary>
|
||||
public int RemainingPages { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 连续打卡天数
|
||||
/// </summary>
|
||||
public int ConsecutiveDays { get; set; }
|
||||
}
|
||||
@ -0,0 +1,32 @@
|
||||
namespace QYZH.InteractiveMagazine.Models.Dto.UserAnswerTaskService;
|
||||
|
||||
/// <summary>
|
||||
/// 二级栏目输出
|
||||
/// </summary>
|
||||
public class SecondCatalogOutput
|
||||
{
|
||||
/// <summary>
|
||||
/// 二级栏目ID
|
||||
/// </summary>
|
||||
public long CatalogId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 二级栏目名称
|
||||
/// </summary>
|
||||
public string CatalogName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 总积分值
|
||||
/// </summary>
|
||||
public int TotalPoints { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 总成长值
|
||||
/// </summary>
|
||||
public int TotalGrowthPoints { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 状态:未开始 / 进行中 / 已完成
|
||||
/// </summary>
|
||||
public string Status { get; set; }
|
||||
}
|
||||
@ -0,0 +1,37 @@
|
||||
namespace QYZH.InteractiveMagazine.Models.Dto.UserAnswerTaskService;
|
||||
|
||||
/// <summary>
|
||||
/// 二级目录详情输出(含页码范围、任务数、完成状态)
|
||||
/// </summary>
|
||||
public class SubCatalogDetailOutput
|
||||
{
|
||||
/// <summary>
|
||||
/// 二级目录ID
|
||||
/// </summary>
|
||||
public long CatalogId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 二级目录名字
|
||||
/// </summary>
|
||||
public string SecondLevelCatalogName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 起始页码(杂志第几页开始)
|
||||
/// </summary>
|
||||
public int StartPage { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 结束页码(杂志第几页结束)
|
||||
/// </summary>
|
||||
public int EndPage { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 任务数量
|
||||
/// </summary>
|
||||
public int TaskCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 状态描述(格式如:1/2 进行中、2/2 已完成、0/2 未开始)
|
||||
/// </summary>
|
||||
public string Status { get; set; }
|
||||
}
|
||||
@ -0,0 +1,27 @@
|
||||
namespace QYZH.InteractiveMagazine.Models.Dto.UserAnswerTaskService;
|
||||
|
||||
/// <summary>
|
||||
/// 任务状态输出
|
||||
/// </summary>
|
||||
public class TaskStatusOutput
|
||||
{
|
||||
/// <summary>
|
||||
/// 任务ID
|
||||
/// </summary>
|
||||
public long TaskId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 任务名字
|
||||
/// </summary>
|
||||
public string TaskName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 任务状态:未开始 / 进行中 / 已完成
|
||||
/// </summary>
|
||||
public string Status { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 答题时间(分钟,由 AnswerSeconds 累加后转换)
|
||||
/// </summary>
|
||||
public double AnswerTime { get; set; }
|
||||
}
|
||||
@ -0,0 +1,68 @@
|
||||
namespace QYZH.InteractiveMagazine.Models.Dto.UserAnswerTaskService;
|
||||
|
||||
/// <summary>
|
||||
/// 用户答题任务列表输出
|
||||
/// </summary>
|
||||
public class UserAnswerTaskListOutput
|
||||
{
|
||||
/// <summary>
|
||||
/// 一级栏目名称
|
||||
/// </summary>
|
||||
public string FirstLevelCatalogName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 目录名称(二级栏目)
|
||||
/// </summary>
|
||||
public string CatalogName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 开始页码
|
||||
/// </summary>
|
||||
public int StartPage { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 结束页码
|
||||
/// </summary>
|
||||
public int EndPage { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 任务列表
|
||||
/// </summary>
|
||||
public List<UserAnswerTaskItemOutput> Tasks { get; set; } = new();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 用户答题任务项输出
|
||||
/// </summary>
|
||||
public class UserAnswerTaskItemOutput
|
||||
{
|
||||
/// <summary>
|
||||
/// 任务GroupId
|
||||
/// </summary>
|
||||
public long GroupId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 任务名称
|
||||
/// </summary>
|
||||
public string TaskName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 总成长值
|
||||
/// </summary>
|
||||
public int TotalGrowthPoints { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 总积分
|
||||
/// </summary>
|
||||
public int TotalPoints { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 答题时间(分钟)
|
||||
/// </summary>
|
||||
public double AnswerMinutes { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 任务状态(进行中、已完成、未开始)
|
||||
/// </summary>
|
||||
public string Status { get; set; }
|
||||
}
|
||||
@ -26,6 +26,11 @@ public class UsersOutput
|
||||
/// </summary>
|
||||
public string WxUserId { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 微信用户名
|
||||
/// </summary>
|
||||
public string? WxUserName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 昵称
|
||||
/// </summary>
|
||||
@ -55,6 +60,11 @@ public class UsersOutput
|
||||
/// 当前成长值
|
||||
/// </summary>
|
||||
public int GrowthPoints { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 用户作答信息上传地址
|
||||
/// </summary>
|
||||
public string UploadDomain { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -86,7 +96,7 @@ public class UserJournalItemOutput
|
||||
/// <summary>
|
||||
/// 期刊标题
|
||||
/// </summary>
|
||||
public string JournalTitle { get; set; } = string.Empty;
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 期刊封面
|
||||
|
||||
@ -146,6 +146,11 @@ public class WxUserOutput
|
||||
/// 创建时间
|
||||
/// </summary>
|
||||
public DateTime CreatedAt { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 用户作答信息上传地址
|
||||
/// </summary>
|
||||
public string UploadDomain { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
66
QYZH.InteractiveMagazine.Models/Entity/AdminMenu.cs
Normal file
66
QYZH.InteractiveMagazine.Models/Entity/AdminMenu.cs
Normal file
@ -0,0 +1,66 @@
|
||||
using SqlSugar;
|
||||
|
||||
namespace QYZH.InteractiveMagazine.Models.Entity;
|
||||
|
||||
/// <summary>
|
||||
/// 后台菜单表
|
||||
/// </summary>
|
||||
[SugarTable("System_AdminMenu")]
|
||||
public partial class AdminMenu : SqlSugarBaseEntity
|
||||
{
|
||||
/// <summary>
|
||||
/// Desc:父级菜单ID,根节点为0
|
||||
/// Default:0
|
||||
/// Nullable:False
|
||||
/// </summary>
|
||||
public long ParentId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:菜单名称
|
||||
/// Default:
|
||||
/// Nullable:False
|
||||
/// </summary>
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Desc:权限编码
|
||||
/// Default:
|
||||
/// Nullable:False
|
||||
/// </summary>
|
||||
public string Code { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Desc:前端路由路径
|
||||
/// Default:
|
||||
/// Nullable:True
|
||||
/// </summary>
|
||||
public string? Path { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:前端组件路径
|
||||
/// Default:
|
||||
/// Nullable:True
|
||||
/// </summary>
|
||||
public string? Component { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:菜单图标
|
||||
/// Default:
|
||||
/// Nullable:True
|
||||
/// </summary>
|
||||
public string? Icon { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:排序值
|
||||
/// Default:0
|
||||
/// Nullable:False
|
||||
/// </summary>
|
||||
public int Sort { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:是否显示在菜单
|
||||
/// Default:b'1'
|
||||
/// Nullable:False
|
||||
/// </summary>
|
||||
public bool IsVisible { get; set; } = true;
|
||||
}
|
||||
39
QYZH.InteractiveMagazine.Models/Entity/AdminRole.cs
Normal file
39
QYZH.InteractiveMagazine.Models/Entity/AdminRole.cs
Normal file
@ -0,0 +1,39 @@
|
||||
using SqlSugar;
|
||||
using QYZH.InteractiveMagazine.Models.Enum;
|
||||
|
||||
namespace QYZH.InteractiveMagazine.Models.Entity;
|
||||
|
||||
/// <summary>
|
||||
/// 后台角色表
|
||||
/// </summary>
|
||||
[SugarTable("System_AdminRole")]
|
||||
public partial class AdminRole : SqlSugarBaseEntity
|
||||
{
|
||||
/// <summary>
|
||||
/// Desc:角色名称
|
||||
/// Default:
|
||||
/// Nullable:False
|
||||
/// </summary>
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Desc:角色编码
|
||||
/// Default:
|
||||
/// Nullable:False
|
||||
/// </summary>
|
||||
public string Code { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Desc:角色类型,0=普通角色,1=系统角色
|
||||
/// Default:0
|
||||
/// Nullable:False
|
||||
/// </summary>
|
||||
public AdminRoleTypeEnum Type { get; set; } = AdminRoleTypeEnum.Normal;
|
||||
|
||||
/// <summary>
|
||||
/// Desc:备注
|
||||
/// Default:
|
||||
/// Nullable:True
|
||||
/// </summary>
|
||||
public string? Remark { get; set; }
|
||||
}
|
||||
24
QYZH.InteractiveMagazine.Models/Entity/AdminRoleMenu.cs
Normal file
24
QYZH.InteractiveMagazine.Models/Entity/AdminRoleMenu.cs
Normal file
@ -0,0 +1,24 @@
|
||||
using SqlSugar;
|
||||
|
||||
namespace QYZH.InteractiveMagazine.Models.Entity;
|
||||
|
||||
/// <summary>
|
||||
/// 后台角色菜单关系表
|
||||
/// </summary>
|
||||
[SugarTable("System_AdminRoleMenu")]
|
||||
public partial class AdminRoleMenu : SqlSugarBaseEntity
|
||||
{
|
||||
/// <summary>
|
||||
/// Desc:角色ID
|
||||
/// Default:
|
||||
/// Nullable:False
|
||||
/// </summary>
|
||||
public long RoleId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:菜单ID
|
||||
/// Default:
|
||||
/// Nullable:False
|
||||
/// </summary>
|
||||
public long MenuId { get; set; }
|
||||
}
|
||||
@ -6,7 +6,7 @@ namespace QYZH.InteractiveMagazine.Models.Entity
|
||||
///<summary>
|
||||
///后台管理员表
|
||||
///</summary>
|
||||
[SugarTable("AdminUser")]
|
||||
[SugarTable("System_AdminUser")]
|
||||
public partial class AdminUser : SqlSugarBaseEntity
|
||||
{
|
||||
public AdminUser(){
|
||||
|
||||
24
QYZH.InteractiveMagazine.Models/Entity/AdminUserRole.cs
Normal file
24
QYZH.InteractiveMagazine.Models/Entity/AdminUserRole.cs
Normal file
@ -0,0 +1,24 @@
|
||||
using SqlSugar;
|
||||
|
||||
namespace QYZH.InteractiveMagazine.Models.Entity;
|
||||
|
||||
/// <summary>
|
||||
/// 后台管理员角色关系表
|
||||
/// </summary>
|
||||
[SugarTable("System_AdminUserRole")]
|
||||
public partial class AdminUserRole : SqlSugarBaseEntity
|
||||
{
|
||||
/// <summary>
|
||||
/// Desc:管理员ID
|
||||
/// Default:
|
||||
/// Nullable:False
|
||||
/// </summary>
|
||||
public long AdminUserId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:角色ID
|
||||
/// Default:
|
||||
/// Nullable:False
|
||||
/// </summary>
|
||||
public long RoleId { get; set; }
|
||||
}
|
||||
@ -8,7 +8,7 @@ namespace QYZH.InteractiveMagazine.Models.Entity
|
||||
///<summary>
|
||||
///AIPrompt配置表
|
||||
///</summary>
|
||||
[SugarTable("AiBasePrompt")]
|
||||
[SugarTable("Ai_BasePrompt")]
|
||||
public partial class AiBasePrompt : SqlSugarBaseEntity
|
||||
{
|
||||
public AiBasePrompt(){
|
||||
|
||||
74
QYZH.InteractiveMagazine.Models/Entity/Banner.cs
Normal file
74
QYZH.InteractiveMagazine.Models/Entity/Banner.cs
Normal file
@ -0,0 +1,74 @@
|
||||
using QYZH.InteractiveMagazine.Models.Enum;
|
||||
using SqlSugar;
|
||||
|
||||
namespace QYZH.InteractiveMagazine.Models.Entity;
|
||||
|
||||
/// <summary>
|
||||
/// 轮播图表
|
||||
/// </summary>
|
||||
[SugarTable("Content_Banner")]
|
||||
public partial class Banner : SqlSugarBaseEntity
|
||||
{
|
||||
/// <summary>
|
||||
/// Desc:标题
|
||||
/// Default:
|
||||
/// Nullable:False
|
||||
/// </summary>
|
||||
public string Title { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Desc:轮播图OSS路径
|
||||
/// Default:
|
||||
/// Nullable:False
|
||||
/// </summary>
|
||||
public string ImageUrl { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Desc:展示位置
|
||||
/// Default:1
|
||||
/// Nullable:False
|
||||
/// </summary>
|
||||
public BannerPositionEnum Position { get; set; } = BannerPositionEnum.Home;
|
||||
|
||||
/// <summary>
|
||||
/// Desc:跳转类型
|
||||
/// Default:0
|
||||
/// Nullable:False
|
||||
/// </summary>
|
||||
public BannerTargetTypeEnum TargetType { get; set; } = BannerTargetTypeEnum.None;
|
||||
|
||||
/// <summary>
|
||||
/// Desc:跳转值
|
||||
/// Default:
|
||||
/// Nullable:True
|
||||
/// </summary>
|
||||
public string? TargetValue { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:排序值,越小越靠前
|
||||
/// Default:0
|
||||
/// Nullable:False
|
||||
/// </summary>
|
||||
public int Sort { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:生效开始时间
|
||||
/// Default:
|
||||
/// Nullable:True
|
||||
/// </summary>
|
||||
public DateTime? StartTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:生效结束时间
|
||||
/// Default:
|
||||
/// Nullable:True
|
||||
/// </summary>
|
||||
public DateTime? EndTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:备注
|
||||
/// Default:
|
||||
/// Nullable:True
|
||||
/// </summary>
|
||||
public string? Remark { get; set; }
|
||||
}
|
||||
@ -6,7 +6,7 @@ namespace QYZH.InteractiveMagazine.Models.Entity
|
||||
///<summary>
|
||||
///签到配置表
|
||||
///</summary>
|
||||
[SugarTable("CheckInConfig")]
|
||||
[SugarTable("CheckIn_Config")]
|
||||
public partial class CheckInConfig : SqlSugarBaseEntity
|
||||
{
|
||||
public CheckInConfig(){
|
||||
|
||||
@ -6,7 +6,7 @@ namespace QYZH.InteractiveMagazine.Models.Entity
|
||||
///<summary>
|
||||
///签到记录表
|
||||
///</summary>
|
||||
[SugarTable("CheckInRecord")]
|
||||
[SugarTable("CheckIn_Record")]
|
||||
public partial class CheckInRecord : SqlSugarBaseEntity
|
||||
{
|
||||
public CheckInRecord(){
|
||||
|
||||
@ -6,7 +6,7 @@ namespace QYZH.InteractiveMagazine.Models.Entity
|
||||
///<summary>
|
||||
///社区预置消息表
|
||||
///</summary>
|
||||
[SugarTable("CommunityMessage")]
|
||||
[SugarTable("Community_Message")]
|
||||
public partial class CommunityMessage : SqlSugarBaseEntity
|
||||
{
|
||||
public CommunityMessage()
|
||||
|
||||
@ -6,7 +6,7 @@ namespace QYZH.InteractiveMagazine.Models.Entity
|
||||
///<summary>
|
||||
///模板评论表
|
||||
///</summary>
|
||||
[SugarTable("CommunityMessageComment")]
|
||||
[SugarTable("Community_MessageComment")]
|
||||
public partial class CommunityMessageComment : SqlSugarBaseEntity
|
||||
{
|
||||
public CommunityMessageComment(){
|
||||
|
||||
@ -6,7 +6,7 @@ namespace QYZH.InteractiveMagazine.Models.Entity
|
||||
///<summary>
|
||||
///点赞记录表
|
||||
///</summary>
|
||||
[SugarTable("MessageLike")]
|
||||
[SugarTable("Community_MessageLike")]
|
||||
public partial class CommunityMessageLike : SqlSugarBaseEntity
|
||||
{
|
||||
public CommunityMessageLike()
|
||||
|
||||
@ -6,7 +6,7 @@ namespace QYZH.InteractiveMagazine.Models.Entity
|
||||
///<summary>
|
||||
///固定模板言论表
|
||||
///</summary>
|
||||
[SugarTable("TemplateSentence")]
|
||||
[SugarTable("Community_TemplateSentence")]
|
||||
public partial class CommunityTemplateSentence : SqlSugarBaseEntity
|
||||
{
|
||||
public CommunityTemplateSentence(){
|
||||
|
||||
@ -6,7 +6,7 @@ namespace QYZH.InteractiveMagazine.Models.Entity
|
||||
/// <summary>
|
||||
/// 补偿任务表 — 记录业务执行失败后需要异步重试的操作
|
||||
/// </summary>
|
||||
[SugarTable("CompensationTask")]
|
||||
[SugarTable("Compensation_Task")]
|
||||
public partial class CompensationTask : SqlSugarBaseEntity
|
||||
{
|
||||
public CompensationTask() { }
|
||||
|
||||
@ -9,7 +9,7 @@ namespace QYZH.InteractiveMagazine.Models.Entity
|
||||
///<summary>
|
||||
///
|
||||
///</summary>
|
||||
[SugarTable("DotFile")]
|
||||
[SugarTable("Journal_DotFile")]
|
||||
public partial class DotFile : SqlSugarBaseEntity
|
||||
{
|
||||
public DotFile(){
|
||||
|
||||
@ -9,7 +9,7 @@ namespace QYZH.InteractiveMagazine.Models.Entity
|
||||
///<summary>
|
||||
///
|
||||
///</summary>
|
||||
[SugarTable("DotFileDetail")]
|
||||
[SugarTable("Journal_DotFileDetail")]
|
||||
public partial class DotFileDetail : SqlSugarBaseEntity
|
||||
{
|
||||
public DotFileDetail(){
|
||||
|
||||
@ -6,7 +6,7 @@ namespace QYZH.InteractiveMagazine.Models.Entity
|
||||
/// <summary>
|
||||
/// 商品兑换记录表
|
||||
/// </summary>
|
||||
[SugarTable("ExchangeRecord")]
|
||||
[SugarTable("Mall_ExchangeRecord")]
|
||||
public partial class ExchangeRecord : SqlSugarBaseEntity
|
||||
{
|
||||
public ExchangeRecord() { }
|
||||
|
||||
@ -6,7 +6,7 @@ namespace QYZH.InteractiveMagazine.Models.Entity
|
||||
///<summary>
|
||||
///期刊表
|
||||
///</summary>
|
||||
[SugarTable("Journal")]
|
||||
[SugarTable("Journal_Book")]
|
||||
public partial class Journal : SqlSugarBaseEntity
|
||||
{
|
||||
public Journal(){
|
||||
@ -20,6 +20,13 @@ namespace QYZH.InteractiveMagazine.Models.Entity
|
||||
/// </summary>
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:标题
|
||||
/// Default:
|
||||
/// Nullable:True
|
||||
/// </summary>
|
||||
public string Title { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:总页数
|
||||
/// Default:
|
||||
@ -92,8 +99,6 @@ namespace QYZH.InteractiveMagazine.Models.Entity
|
||||
public string PdfPreviewUrl { get; set; }
|
||||
|
||||
|
||||
public string Title { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:下载书籍页码点阵码PDF文件名称
|
||||
/// Default:
|
||||
@ -101,6 +106,20 @@ namespace QYZH.InteractiveMagazine.Models.Entity
|
||||
/// </summary>
|
||||
public string DownloadJournalPagePdfName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:发布日期开始时间
|
||||
/// Default:
|
||||
/// Nullable:True
|
||||
/// </summary>
|
||||
public DateTime? StartTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:发布日期结束时间
|
||||
/// Default:
|
||||
/// Nullable:True
|
||||
/// </summary>
|
||||
public DateTime? EndTime { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Desc:书籍描述/简介
|
||||
|
||||
@ -9,7 +9,7 @@ namespace QYZH.InteractiveMagazine.Models.Entity
|
||||
///<summary>
|
||||
///
|
||||
///</summary>
|
||||
[SugarTable("JournalCatalog")]
|
||||
[SugarTable("Journal_Catalog")]
|
||||
public partial class JournalCatalog : SqlSugarBaseEntity
|
||||
{
|
||||
public JournalCatalog(){
|
||||
|
||||
@ -8,7 +8,7 @@ namespace QYZH.InteractiveMagazine.Models.Entity
|
||||
///<summary>
|
||||
///书页;
|
||||
///</summary>
|
||||
[SugarTable("JournalPage")]
|
||||
[SugarTable("Journal_Page")]
|
||||
public partial class JournalPage : SqlSugarBaseEntity
|
||||
{
|
||||
public JournalPage(){
|
||||
|
||||
@ -1,156 +1,130 @@
|
||||
using QYZH.InteractiveMagazine.Models.Enum;
|
||||
using SqlSugar;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace QYZH.InteractiveMagazine.Models.Entity
|
||||
{
|
||||
///<summary>
|
||||
///杂志任务;
|
||||
///</summary>
|
||||
[SugarTable("JournalPageTask")]
|
||||
public partial class JournalPageTask : SqlSugarBaseEntity
|
||||
{
|
||||
public JournalPageTask(){
|
||||
[SugarTable("Journal_PageTask")]
|
||||
public class JournalPageTask : SqlSugarBaseEntity
|
||||
{
|
||||
/// <summary>
|
||||
/// Desc:书id
|
||||
/// Default:
|
||||
/// Nullable:False
|
||||
/// </summary>
|
||||
public long JournalId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:书页id
|
||||
/// Default:
|
||||
/// Nullable:False
|
||||
/// </summary>
|
||||
public long JournalPageId { get; set; }
|
||||
/// <summary>
|
||||
/// Desc:分组
|
||||
/// Default:
|
||||
/// Nullable:False
|
||||
/// </summary>
|
||||
public long GroupId { get; set; }
|
||||
/// <summary>
|
||||
/// Desc:题号
|
||||
/// Default:
|
||||
/// Nullable:True
|
||||
/// </summary>
|
||||
public string No { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:题型
|
||||
/// Default:
|
||||
/// Nullable:False
|
||||
/// </summary>
|
||||
public TaskBankTypeEnum Type { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:任务
|
||||
/// Default:
|
||||
/// Nullable:True
|
||||
/// </summary>
|
||||
public string Task { get; set; }
|
||||
|
||||
|
||||
}
|
||||
/// <summary>
|
||||
/// Desc:成长值设置
|
||||
/// Default:0
|
||||
/// Nullable:True
|
||||
/// </summary>
|
||||
public int GrowthPoint { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:书id
|
||||
/// Default:
|
||||
/// Nullable:False
|
||||
/// </summary>
|
||||
public long JournalId {get;set;}
|
||||
/// <summary>
|
||||
/// Desc:积分设置
|
||||
/// Default:0
|
||||
/// Nullable:True
|
||||
/// </summary>
|
||||
public int Points { get; set; }
|
||||
/// <summary>
|
||||
/// Desc:理解力评分
|
||||
/// Default:0
|
||||
/// Nullable:False
|
||||
/// </summary>
|
||||
public float Comprehension { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:书页id
|
||||
/// Default:
|
||||
/// Nullable:False
|
||||
/// </summary>
|
||||
public long JournalPageId {get;set;}
|
||||
/// <summary>
|
||||
/// Desc:判断力评分
|
||||
/// Default:0
|
||||
/// Nullable:False
|
||||
/// </summary>
|
||||
public float Judgment { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:题号
|
||||
/// Default:
|
||||
/// Nullable:True
|
||||
/// </summary>
|
||||
public string No {get;set;}
|
||||
/// <summary>
|
||||
/// Desc:表达力评分
|
||||
/// Default:0
|
||||
/// Nullable:False
|
||||
/// </summary>
|
||||
public float Expression { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:题型
|
||||
/// Default:
|
||||
/// Nullable:False
|
||||
/// </summary>
|
||||
public TaskBankTypeEnum Type {get;set;}
|
||||
/// <summary>
|
||||
/// Desc:说服力评分
|
||||
/// Default:0
|
||||
/// Nullable:False
|
||||
/// </summary>
|
||||
public float Persuasiveness { get; set; }
|
||||
/// <summary>
|
||||
/// Desc:乐观锁
|
||||
/// Default:
|
||||
/// Nullable:False
|
||||
/// </summary>
|
||||
public int Revision { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:任务
|
||||
/// Default:
|
||||
/// Nullable:True
|
||||
/// </summary>
|
||||
public string Task {get;set;}
|
||||
|
||||
/// <summary>
|
||||
/// Desc:答案
|
||||
/// Default:
|
||||
/// Nullable:True
|
||||
/// </summary>
|
||||
public string Answer {get;set;}
|
||||
|
||||
/// <summary>
|
||||
/// Desc:解析
|
||||
/// Default:
|
||||
/// Nullable:True
|
||||
/// </summary>
|
||||
public string Analysis {get;set;}
|
||||
/// <summary>
|
||||
/// Desc:任务图片地址
|
||||
/// Default:
|
||||
/// Nullable:True
|
||||
/// </summary>
|
||||
public string TaskUrl { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Desc:分组
|
||||
/// Default:
|
||||
/// Nullable:False
|
||||
/// </summary>
|
||||
public long GroupId {get;set;}
|
||||
/// <summary>
|
||||
/// Desc:是否需要AI处理
|
||||
/// Default:true
|
||||
/// Nullable:False
|
||||
/// </summary>
|
||||
public bool NeedAiProcess { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:乐观锁
|
||||
/// Default:
|
||||
/// Nullable:False
|
||||
/// </summary>
|
||||
public int Revision {get;set;}
|
||||
/// <summary>
|
||||
/// Desc:Prompt配置
|
||||
/// Default:
|
||||
/// Nullable:True
|
||||
/// </summary>
|
||||
public string Prompt { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:任务图片地址
|
||||
/// Default:
|
||||
/// Nullable:True
|
||||
/// </summary>
|
||||
public string TaskUrl {get;set;}
|
||||
|
||||
/// <summary>
|
||||
/// Desc:任务分数
|
||||
/// Default:0
|
||||
/// Nullable:False
|
||||
/// </summary>
|
||||
public float Score {get;set;}
|
||||
|
||||
/// <summary>
|
||||
/// Desc:选项
|
||||
/// Default:
|
||||
/// Nullable:True
|
||||
/// </summary>
|
||||
public string Options {get;set;}
|
||||
|
||||
/// <summary>
|
||||
/// Desc:回答时间(秒)
|
||||
/// Default:0
|
||||
/// Nullable:False
|
||||
/// </summary>
|
||||
public int AnswerTime {get;set;}
|
||||
|
||||
/// <summary>
|
||||
/// Desc:关键解析json
|
||||
/// Default:
|
||||
/// Nullable:True
|
||||
/// </summary>
|
||||
public string KeywordAnalysis {get;set;}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Desc:成长值设置
|
||||
/// Default:0
|
||||
/// Nullable:True
|
||||
/// </summary>
|
||||
public int? GrowthPoint {get;set;}
|
||||
|
||||
/// <summary>
|
||||
/// Desc:积分设置
|
||||
/// Default:0
|
||||
/// Nullable:True
|
||||
/// </summary>
|
||||
public int? Points {get;set;}
|
||||
|
||||
/// <summary>
|
||||
/// Desc:是否需要AI处理
|
||||
/// Default:true
|
||||
/// Nullable:False
|
||||
/// </summary>
|
||||
public bool NeedAiProcess {get;set;}
|
||||
|
||||
/// <summary>
|
||||
/// Desc:基础Prompt配置ID(为空则使用IsDefault=1的模板)
|
||||
/// Default:
|
||||
/// Nullable:True
|
||||
/// </summary>
|
||||
public long? BasePromptId {get;set;}
|
||||
|
||||
/// <summary>
|
||||
/// Desc:个性化Prompt(追加到基础Prompt后面,可为空)
|
||||
/// Default:
|
||||
/// Nullable:True
|
||||
/// </summary>
|
||||
public string CustomPrompt {get;set;}
|
||||
/// <summary>
|
||||
/// Desc:作答参考时间
|
||||
/// Default:
|
||||
/// Nullable:True
|
||||
/// </summary>
|
||||
public int AnswerTime { get; set; }
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,41 @@
|
||||
using SqlSugar;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace QYZH.InteractiveMagazine.Models.Entity
|
||||
{
|
||||
///<summary>
|
||||
///杂志任务答案表;
|
||||
///</summary>
|
||||
[SugarTable("Journal_PageTaskAnswer")]
|
||||
public partial class JournalPageTaskAnswer : SqlSugarBaseEntity
|
||||
{
|
||||
public JournalPageTaskAnswer(){
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Desc:任务id
|
||||
/// Default:
|
||||
/// Nullable:False
|
||||
/// </summary>
|
||||
public long JournalPageTaskId {get;set;}
|
||||
|
||||
/// <summary>
|
||||
/// Desc:答案
|
||||
/// Default:
|
||||
/// Nullable:True
|
||||
/// </summary>
|
||||
public string Answer {get;set;}
|
||||
|
||||
/// <summary>
|
||||
/// Desc:解析
|
||||
/// Default:
|
||||
/// Nullable:True
|
||||
/// </summary>
|
||||
public string AnswerUrL { get;set;}
|
||||
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,236 @@
|
||||
using QYZH.InteractiveMagazine.Models.Enum;
|
||||
using SqlSugar;
|
||||
|
||||
namespace QYZH.InteractiveMagazine.Models.Entity
|
||||
{
|
||||
/// <summary>
|
||||
/// 杂志任务用户作答记录
|
||||
/// </summary>
|
||||
[SugarTable("Journal_PageTaskUserAnswer")]
|
||||
public class JournalPageTaskUserAnswer : SqlSugarBaseEntity
|
||||
{
|
||||
/// <summary>
|
||||
/// Desc:书id
|
||||
/// Default:
|
||||
/// Nullable:False
|
||||
/// </summary>
|
||||
public long JournalId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:书页id
|
||||
/// Default:
|
||||
/// Nullable:False
|
||||
/// </summary>
|
||||
public long JournalPageId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:任务id
|
||||
/// Default:
|
||||
/// Nullable:False
|
||||
/// </summary>
|
||||
public long JournalPageTaskId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:任务分组id
|
||||
/// Default:
|
||||
/// Nullable:False
|
||||
/// </summary>
|
||||
public long JournalPageTaskGroupId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:用户id
|
||||
/// Default:
|
||||
/// Nullable:False
|
||||
/// </summary>
|
||||
public long UserId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:作答结果
|
||||
/// Default:
|
||||
/// Nullable:True
|
||||
/// </summary>
|
||||
public string Result { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:积分
|
||||
/// Default:0
|
||||
/// Nullable:False
|
||||
/// </summary>
|
||||
public int Points { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:成长值
|
||||
/// Default:0
|
||||
/// Nullable:False
|
||||
/// </summary>
|
||||
public int GrowthPoints { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:题目得分
|
||||
/// Default:0
|
||||
/// Nullable:False
|
||||
/// </summary>
|
||||
public float Score { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:理解力评分
|
||||
/// Default:0
|
||||
/// Nullable:False
|
||||
/// </summary>
|
||||
public float Comprehension { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:判断力评分
|
||||
/// Default:0
|
||||
/// Nullable:False
|
||||
/// </summary>
|
||||
public float Judgment { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:表达力评分
|
||||
/// Default:0
|
||||
/// Nullable:False
|
||||
/// </summary>
|
||||
public float Expression { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:说服力评分
|
||||
/// Default:0
|
||||
/// Nullable:False
|
||||
/// </summary>
|
||||
public float Persuasiveness { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:题目作答图片地址
|
||||
/// Default:
|
||||
/// Nullable:True
|
||||
/// </summary>
|
||||
public string QuestionAnswerUrl { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:答案图片地址
|
||||
/// Default:
|
||||
/// Nullable:True
|
||||
/// </summary>
|
||||
public string AnswerUrl { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:页面作答图片地址
|
||||
/// Default:
|
||||
/// Nullable:True
|
||||
/// </summary>
|
||||
public string PageAnswerUrl { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:乐观锁
|
||||
/// Default:
|
||||
/// Nullable:False
|
||||
/// </summary>
|
||||
public int Revision { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:作答开始时间
|
||||
/// Default:
|
||||
/// Nullable:True
|
||||
/// </summary>
|
||||
public DateTime AnswerStartTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:作答结束时间
|
||||
/// Default:
|
||||
/// Nullable:True
|
||||
/// </summary>
|
||||
public DateTime AnswerEndTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:作答耗时(秒)
|
||||
/// Default:0
|
||||
/// Nullable:False
|
||||
/// </summary>
|
||||
public int AnswerSeconds { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:图像识别结果
|
||||
/// Default:
|
||||
/// Nullable:False
|
||||
/// </summary>
|
||||
public int ImageRecognition { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:书页序号
|
||||
/// Default:
|
||||
/// Nullable:False
|
||||
/// </summary>
|
||||
public int JournalPageNum { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:修改次数
|
||||
/// Default:
|
||||
/// Nullable:False
|
||||
/// </summary>
|
||||
public int Modify { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:最后标签id
|
||||
/// Default:
|
||||
/// Nullable:False
|
||||
/// </summary>
|
||||
public long LastTag { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:点读书页序号
|
||||
/// Default:
|
||||
/// Nullable:False
|
||||
/// </summary>
|
||||
public int DotPageNum { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:页面结果图片地址
|
||||
/// Default:
|
||||
/// Nullable:True
|
||||
/// </summary>
|
||||
public string PageResultUrl { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:题型
|
||||
/// Default:
|
||||
/// Nullable:True
|
||||
/// </summary>
|
||||
public string Type { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:点读书页编号
|
||||
/// Default:
|
||||
/// Nullable:True
|
||||
/// </summary>
|
||||
public string DotPageNo { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:页面作答点读图片地址
|
||||
/// Default:
|
||||
/// Nullable:True
|
||||
/// </summary>
|
||||
public string PageAnswerDotUrl { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:中断次数
|
||||
/// Default:0
|
||||
/// Nullable:False
|
||||
/// </summary>
|
||||
public int BreakCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:中断时间记录
|
||||
/// Default:
|
||||
/// Nullable:True
|
||||
/// </summary>
|
||||
public string BreakTimes { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:作业状态
|
||||
/// Default:
|
||||
/// Nullable:True
|
||||
/// </summary>
|
||||
public int AssignmentStatus { get; set; }
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,176 @@
|
||||
using SqlSugar;
|
||||
|
||||
namespace QYZH.InteractiveMagazine.Models.Entity
|
||||
{
|
||||
/// <summary>
|
||||
/// Snapshot record for a user answer.
|
||||
/// </summary>
|
||||
[SugarTable("Journal_PageTaskUserAnswerSnapshot")]
|
||||
public class JournalPageTaskUserAnswerSnapshot : SqlSugarBaseEntity
|
||||
{
|
||||
/// <summary>
|
||||
/// Source JournalPageTaskUserAnswer record Id.
|
||||
/// </summary>
|
||||
public long JournalPageTaskUserAnswerId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Journal Id.
|
||||
/// </summary>
|
||||
public long JournalId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Journal page Id.
|
||||
/// </summary>
|
||||
public long JournalPageId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Journal page task Id.
|
||||
/// </summary>
|
||||
public long JournalPageTaskId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Journal page task group Id.
|
||||
/// </summary>
|
||||
public long JournalPageTaskGroupId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// User Id.
|
||||
/// </summary>
|
||||
public long UserId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Answer result.
|
||||
/// </summary>
|
||||
public string? Result { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Points awarded.
|
||||
/// </summary>
|
||||
public int Points { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Growth points awarded.
|
||||
/// </summary>
|
||||
public float GrowthPoints { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Task score.
|
||||
/// </summary>
|
||||
public float Score { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Comprehension score.
|
||||
/// </summary>
|
||||
public float Comprehension { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Judgment score.
|
||||
/// </summary>
|
||||
public float Judgment { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Expression score.
|
||||
/// </summary>
|
||||
public float Expression { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Persuasiveness score.
|
||||
/// </summary>
|
||||
public float Persuasiveness { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Question answer image URL.
|
||||
/// </summary>
|
||||
public string? QuestionAnswerUrl { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Answer image URL.
|
||||
/// </summary>
|
||||
public string? AnswerUrl { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Page answer image URL.
|
||||
/// </summary>
|
||||
public string? PageAnswerUrl { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Optimistic concurrency revision.
|
||||
/// </summary>
|
||||
public int Revision { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Answer start time.
|
||||
/// </summary>
|
||||
public DateTime AnswerStartTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Answer end time.
|
||||
/// </summary>
|
||||
public DateTime AnswerEndTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Answer duration in seconds.
|
||||
/// </summary>
|
||||
public int AnswerSeconds { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Image recognition result.
|
||||
/// </summary>
|
||||
public int ImageRecognition { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Journal page number.
|
||||
/// </summary>
|
||||
public int JournalPageNum { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Modify count.
|
||||
/// </summary>
|
||||
public int Modify { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Last tag Id.
|
||||
/// </summary>
|
||||
public long LastTag { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Dot page number.
|
||||
/// </summary>
|
||||
public int DotPageNum { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Page result image URL.
|
||||
/// </summary>
|
||||
public string? PageResultUrl { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Question type.
|
||||
/// </summary>
|
||||
public string? Type { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Dot page number text.
|
||||
/// </summary>
|
||||
public string? DotPageNo { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Page answer dot image URL.
|
||||
/// </summary>
|
||||
public string? PageAnswerDotUrl { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Break count.
|
||||
/// </summary>
|
||||
public int BreakCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Break time records.
|
||||
/// </summary>
|
||||
public string? BreakTimes { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Assignment status.
|
||||
/// </summary>
|
||||
public int? AssignmentStatus { get; set; }
|
||||
}
|
||||
}
|
||||
52
QYZH.InteractiveMagazine.Models/Entity/Material.cs
Normal file
52
QYZH.InteractiveMagazine.Models/Entity/Material.cs
Normal file
@ -0,0 +1,52 @@
|
||||
using SqlSugar;
|
||||
|
||||
namespace QYZH.InteractiveMagazine.Models.Entity;
|
||||
|
||||
/// <summary>
|
||||
/// 资料表
|
||||
/// </summary>
|
||||
[SugarTable("Content_Material")]
|
||||
public partial class Material : SqlSugarBaseEntity
|
||||
{
|
||||
/// <summary>
|
||||
/// Desc:资料标题
|
||||
/// Default:
|
||||
/// Nullable:False
|
||||
/// </summary>
|
||||
public string Title { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Desc:资料名称
|
||||
/// Default:
|
||||
/// Nullable:True
|
||||
/// </summary>
|
||||
public string? Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:资料描述
|
||||
/// Default:
|
||||
/// Nullable:True
|
||||
/// </summary>
|
||||
public string? Description { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:排序值,越小越靠前
|
||||
/// Default:0
|
||||
/// Nullable:False
|
||||
/// </summary>
|
||||
public int Sort { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:生效开始时间
|
||||
/// Default:
|
||||
/// Nullable:True
|
||||
/// </summary>
|
||||
public DateTime? StartTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:生效结束时间
|
||||
/// Default:
|
||||
/// Nullable:True
|
||||
/// </summary>
|
||||
public DateTime? EndTime { get; set; }
|
||||
}
|
||||
46
QYZH.InteractiveMagazine.Models/Entity/MaterialFile.cs
Normal file
46
QYZH.InteractiveMagazine.Models/Entity/MaterialFile.cs
Normal file
@ -0,0 +1,46 @@
|
||||
using QYZH.InteractiveMagazine.Models.Enum;
|
||||
using SqlSugar;
|
||||
|
||||
namespace QYZH.InteractiveMagazine.Models.Entity;
|
||||
|
||||
/// <summary>
|
||||
/// 资料文件表
|
||||
/// </summary>
|
||||
[SugarTable("Content_MaterialFile")]
|
||||
public partial class MaterialFile : SqlSugarBaseEntity
|
||||
{
|
||||
/// <summary>
|
||||
/// Desc:资料ID
|
||||
/// Default:
|
||||
/// Nullable:False
|
||||
/// </summary>
|
||||
public long MaterialId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:文件类型
|
||||
/// Default:
|
||||
/// Nullable:False
|
||||
/// </summary>
|
||||
public MaterialFileTypeEnum FileType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:文件OSS路径
|
||||
/// Default:
|
||||
/// Nullable:False
|
||||
/// </summary>
|
||||
public string FileUrl { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Desc:文件名称
|
||||
/// Default:
|
||||
/// Nullable:True
|
||||
/// </summary>
|
||||
public string? FileName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:排序值,越小越靠前
|
||||
/// Default:0
|
||||
/// Nullable:False
|
||||
/// </summary>
|
||||
public int Sort { get; set; }
|
||||
}
|
||||
@ -6,7 +6,7 @@ namespace QYZH.InteractiveMagazine.Models.Entity
|
||||
///<summary>
|
||||
///勋章定义表
|
||||
///</summary>
|
||||
[SugarTable("Medal")]
|
||||
[SugarTable("Medal_Definition")]
|
||||
public partial class Medal : SqlSugarBaseEntity
|
||||
{
|
||||
public Medal()
|
||||
|
||||
@ -8,7 +8,7 @@ namespace QYZH.InteractiveMagazine.Models.Entity
|
||||
///<summary>
|
||||
///勋章获取规则配置表
|
||||
///</summary>
|
||||
[SugarTable("MedalRule")]
|
||||
[SugarTable("Medal_Rule")]
|
||||
public partial class MedalRule : SqlSugarBaseEntity
|
||||
{
|
||||
|
||||
|
||||
60
QYZH.InteractiveMagazine.Models/Entity/MessageOutbox.cs
Normal file
60
QYZH.InteractiveMagazine.Models/Entity/MessageOutbox.cs
Normal file
@ -0,0 +1,60 @@
|
||||
using SqlSugar;
|
||||
|
||||
namespace QYZH.InteractiveMagazine.Models.Entity;
|
||||
|
||||
/// <summary>
|
||||
/// 消息Outbox。
|
||||
/// </summary>
|
||||
[SugarTable("Message_Outbox")]
|
||||
public partial class MessageOutbox : SqlSugarBaseEntity
|
||||
{
|
||||
/// <summary>
|
||||
/// 交换机。
|
||||
/// </summary>
|
||||
public string Exchange { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 队列。
|
||||
/// </summary>
|
||||
public string Queue { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 路由键。
|
||||
/// </summary>
|
||||
public string RoutingKey { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 消息内容JSON。
|
||||
/// </summary>
|
||||
public string Payload { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 重试次数。
|
||||
/// </summary>
|
||||
public int RetryCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 下次重试时间。
|
||||
/// </summary>
|
||||
public DateTime? NextRetryAt { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 发送成功时间。
|
||||
/// </summary>
|
||||
public DateTime? SentAt { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 最后错误。
|
||||
/// </summary>
|
||||
public string? LastError { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 业务类型。
|
||||
/// </summary>
|
||||
public string BusinessType { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 业务ID。
|
||||
/// </summary>
|
||||
public long BusinessId { get; set; }
|
||||
}
|
||||
@ -5,7 +5,7 @@ namespace QYZH.InteractiveMagazine.Models.Entity;
|
||||
/// <summary>
|
||||
/// 操作日志表 — 记录后台管理员的所有手动操作
|
||||
/// </summary>
|
||||
[SugarTable("OperationLog")]
|
||||
[SugarTable("System_OperationLog")]
|
||||
public partial class OperationLog : SqlSugarBaseEntity
|
||||
{
|
||||
public OperationLog() { }
|
||||
|
||||
@ -6,7 +6,7 @@ namespace QYZH.InteractiveMagazine.Models.Entity
|
||||
///<summary>
|
||||
///宠物进化链定义表
|
||||
///</summary>
|
||||
[SugarTable("PetEvolution")]
|
||||
[SugarTable("Pet_Evolution")]
|
||||
public partial class PetEvolution : SqlSugarBaseEntity
|
||||
{
|
||||
public PetEvolution() { }
|
||||
|
||||
@ -6,7 +6,7 @@ namespace QYZH.InteractiveMagazine.Models.Entity
|
||||
///<summary>
|
||||
///宠物喂养记录表
|
||||
///</summary>
|
||||
[SugarTable("PetFeedingRecord")]
|
||||
[SugarTable("Pet_FeedingRecord")]
|
||||
public partial class PetFeedingRecord : SqlSugarBaseEntity
|
||||
{
|
||||
public PetFeedingRecord(){
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user