Compare commits
59 Commits
f29802eb5a
...
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 |
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;
|
||||
@ -1,129 +0,0 @@
|
||||
-- =====================================================
|
||||
-- CommunityMessage 社区预置消息表 - 20条模拟测试数据
|
||||
-- 生成时间: 2026-06-12
|
||||
-- =====================================================
|
||||
|
||||
INSERT INTO [dbo].[CommunityMessage]
|
||||
([Id], [Status], [IsDeleted], [CreatedBy], [CreatedAt], [UpdatedBy], [UpdatedAt],
|
||||
[JournalId], [UserId], [UserJournalId], [JournalTaskId], [JournalTaskAnswerId],
|
||||
[Content], [ImageUrl], [SortOrder], [IsActive], [Type], [LikeCount], [IsFeatured])
|
||||
VALUES
|
||||
-- 1. 文章类型 - 期刊导读
|
||||
(1916234567890001, 1, 0, N'系统管理员', '2026-05-01 09:00:00', NULL, NULL,
|
||||
1001, 5001, 8001, 3001, 6001,
|
||||
N'本期期刊聚焦人工智能在教育领域的创新应用,欢迎各位读者踊跃讨论。',
|
||||
NULL, 1, 1, 1, 42, 1),
|
||||
|
||||
-- 2. 引用类型 - 名人名言
|
||||
(1916234567890002, 1, 0, N'编辑小王', '2026-05-02 10:30:00', NULL, NULL,
|
||||
1001, 5002, 8002, 3001, 6002,
|
||||
N'"教育不是灌输,而是点燃火焰。" —— 苏格拉底',
|
||||
NULL, 2, 1, 2, 88, 1),
|
||||
|
||||
-- 3. 公告类型 - 活动通知
|
||||
(1916234567890003, 1, 0, N'系统管理员', '2026-05-03 14:00:00', N'系统管理员', '2026-05-03 15:00:00',
|
||||
1001, 5003, 8003, 3002, 6003,
|
||||
N'【活动通知】第十二期线上读书分享会将于本周六晚8点举行,主题为"科技与人文的对话",欢迎大家报名参加!',
|
||||
N'/uploads/images/activity_notice_202605.png', 1, 1, 3, 156, 1),
|
||||
|
||||
-- 4. 文章类型 - 读者投稿
|
||||
(1916234567890004, 1, 0, N'读者张三', '2026-05-04 08:15:00', NULL, NULL,
|
||||
1002, 5004, 8004, 3003, 6004,
|
||||
N'阅读了本期的深度报道《数字时代的阅读习惯变迁》,深有感触。作为一个从纸质书时代走过来的人,既感慨科技带来的便利,也怀念翻页时的触感。',
|
||||
NULL, 3, 1, 1, 35, 0),
|
||||
|
||||
-- 5. 引用类型 - 期刊摘录
|
||||
(1916234567890005, 1, 0, N'编辑小李', '2026-05-05 11:20:00', NULL, NULL,
|
||||
1002, 5005, 8005, 3003, 6005,
|
||||
N'摘自本期特稿:"在信息爆炸的时代,深度阅读能力正在成为一种稀缺的核心竞争力。"',
|
||||
N'/uploads/images/quote_bg_01.png', 4, 1, 2, 67, 1),
|
||||
|
||||
-- 6. 文章类型 - 互动讨论
|
||||
(1916234567890006, 1, 0, N'读者李四', '2026-05-06 16:45:00', NULL, NULL,
|
||||
1001, 5006, 8006, 3001, 6006,
|
||||
N'关于本期讨论话题"AI是否会取代教师",我认为技术只是工具,教育的本质是人与人之间灵魂的碰撞,这是机器无法替代的。',
|
||||
NULL, 5, 1, 1, 92, 1),
|
||||
|
||||
-- 7. 公告类型 - 系统维护
|
||||
(1916234567890007, 1, 0, N'系统管理员', '2026-05-07 09:00:00', N'系统管理员', '2026-05-07 09:30:00',
|
||||
1001, 5001, 8001, 3001, 6001,
|
||||
N'【系统公告】平台将于5月10日凌晨2:00-5:00进行系统升级维护,届时服务将短暂中断,请各位读者提前保存阅读进度。',
|
||||
NULL, 1, 1, 3, 23, 0),
|
||||
|
||||
-- 8. 文章类型 - 编辑寄语
|
||||
(1916234567890008, 1, 0, N'主编老陈', '2026-05-08 07:30:00', NULL, NULL,
|
||||
1003, 5007, 8007, 3004, 6007,
|
||||
N'新一期杂志如期而至。本期我们特别策划了"青年创作者专栏",收录了十位90后作家的原创作品,期待听到你们的声音。',
|
||||
N'/uploads/images/editor_letter_05.jpg', 1, 1, 1, 210, 1),
|
||||
|
||||
-- 9. 引用类型 - 读者金句
|
||||
(1916234567890009, 1, 0, N'读者王五', '2026-05-09 20:10:00', NULL, NULL,
|
||||
1002, 5008, 8008, 3003, 6008,
|
||||
N'"每一本杂志都是一扇窗,让我们看到不同的人生风景。" 感谢社区里每一位分享者。',
|
||||
NULL, 6, 1, 2, 54, 0),
|
||||
|
||||
-- 10. 文章类型 - 话题互动
|
||||
(1916234567890010, 1, 0, N'读者赵六', '2026-05-10 13:55:00', NULL, NULL,
|
||||
1003, 5009, 8009, 3004, 6009,
|
||||
N'看完"城市记忆"系列第三篇,想起了小时候胡同里的叫卖声和邻居家的饭菜香。城市化进程中,我们该如何留住这些温暖的记忆?',
|
||||
N'/uploads/images/city_memory_03.jpg', 7, 1, 1, 78, 1),
|
||||
|
||||
-- 11. 公告类型 - 征稿启事
|
||||
(1916234567890011, 1, 0, N'编辑小王', '2026-05-11 10:00:00', N'编辑小王', '2026-05-12 08:00:00',
|
||||
1003, 5010, 8010, 3005, 6010,
|
||||
N'【征稿启事】第十三期"我的阅读故事"主题征稿正式启动!征稿时间:5月11日-6月11日,优秀作品将刊登在杂志特辑中,期待您的来稿。',
|
||||
N'/uploads/images/call_for_papers_2026.png', 2, 1, 3, 189, 1),
|
||||
|
||||
-- 12. 文章类型 - 读书笔记
|
||||
(1916234567890012, 1, 0, N'读者孙七', '2026-05-12 19:30:00', NULL, NULL,
|
||||
1001, 5011, 8011, 3001, 6011,
|
||||
N'本期推荐的三本书我都读完了,最喜欢《思考,快与慢》。作者对直觉思维和理性思维的分析让人醍醐灌顶,强烈推荐给所有社区朋友。',
|
||||
NULL, 8, 1, 1, 45, 0),
|
||||
|
||||
-- 13. 引用类型 - 经典回顾
|
||||
(1916234567890013, 1, 0, N'编辑小李', '2026-05-13 15:00:00', NULL, NULL,
|
||||
1002, 5002, 8002, 3003, 6012,
|
||||
N'回顾创刊号寄语:"我们相信,好的内容值得被慢慢品味。在这个快节奏的时代,让我们一起做慢阅读的实践者。"',
|
||||
N'/uploads/images/anniversary_retro.png', 3, 1, 2, 134, 1),
|
||||
|
||||
-- 14. 文章类型 - 作者互动
|
||||
(1916234567890014, 1, 0, N'作者林清', '2026-05-14 21:45:00', NULL, NULL,
|
||||
1003, 5012, 8012, 3004, 6013,
|
||||
N'感谢大家对《城市记忆》系列的支持和反馈!每一条评论我都认真阅读了。你们的共鸣是我继续创作的最大动力,下一篇将聚焦"消失的声音"。',
|
||||
NULL, 9, 1, 1, 167, 1),
|
||||
|
||||
-- 15. 公告类型 - 获奖公告
|
||||
(1916234567890015, 1, 0, N'系统管理员', '2026-05-15 12:00:00', N'系统管理员', '2026-05-15 14:00:00',
|
||||
1001, 5001, 8001, 3002, 6001,
|
||||
N'【获奖公告】第十一期"最佳读者评论"评选结果揭晓!恭喜读者@书香满园、@夜读人、@思考者三位获得本期精选评论奖,奖品将于一周内寄送。',
|
||||
N'/uploads/images/award_11.jpg', 4, 1, 3, 302, 1),
|
||||
|
||||
-- 16. 文章类型 - 生活随笔
|
||||
(1916234567890016, 1, 0, N'读者周八', '2026-05-16 07:00:00', NULL, NULL,
|
||||
1002, 5013, 8013, 3003, 6014,
|
||||
N'清晨六点,一杯咖啡,一本杂志。这已经成了我三年来雷打不动的习惯。今天的这篇关于极简生活的文章,正好呼应了我最近的生活理念——少即是多。',
|
||||
N'/uploads/images/morning_read.jpg', 10, 1, 1, 29, 0),
|
||||
|
||||
-- 17. 引用类型 - 编辑精选
|
||||
(1916234567890017, 1, 0, N'主编老陈', '2026-05-17 16:30:00', NULL, NULL,
|
||||
1003, 5007, 8007, 3004, 6015,
|
||||
N'本期编辑精选语录:"真正的知识不在于拥有多少信息,而在于能用多少方式去理解世界。" —— 选自专栏文章《认知的边界》',
|
||||
NULL, 5, 1, 2, 95, 1),
|
||||
|
||||
-- 18. 文章类型 - 问题探讨
|
||||
(1916234567890018, 1, 0, N'读者吴九', '2026-05-18 22:15:00', NULL, NULL,
|
||||
1001, 5014, 8014, 3001, 6016,
|
||||
N'想请教大家,本期提到的"元宇宙图书馆"概念,你们觉得在技术层面还有多远?VR阅读体验是否真的能超越传统阅读?期待和大家探讨。',
|
||||
NULL, 11, 1, 1, 61, 0),
|
||||
|
||||
-- 19. 公告类型 - 订阅优惠
|
||||
(1916234567890019, 1, 0, N'系统管理员', '2026-05-19 09:30:00', NULL, NULL,
|
||||
1002, 5003, 8003, 3002, 6003,
|
||||
N'【限时优惠】杂志年度订阅特惠活动开启!5月20日-5月31日期间订阅全年杂志享7折优惠,老用户续费额外赠送限量版精装合集一本。',
|
||||
N'/uploads/images/subscription_promo_2026.png', 6, 1, 3, 278, 0),
|
||||
|
||||
-- 20. 文章类型 - 社区感言
|
||||
(1916234567890020, 1, 0, N'读者郑十', '2026-05-20 18:00:00', NULL, NULL,
|
||||
1003, 5015, 8015, 3004, 6017,
|
||||
N'加入这个阅读社区整整一年了,从最初的潜水到现在的积极互动,在这里认识了很多志同道合的朋友。感谢平台的用心运营,让我们一起在文字中找到归属。',
|
||||
N'/uploads/images/community_anniversary.jpg', 12, 1, 1, 445, 1);
|
||||
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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
@ -49,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>
|
||||
|
||||
@ -34,7 +34,7 @@ public interface IUserAnswerTaskService : IBaseService<JournalPageTaskUserAnswer
|
||||
/// <summary>
|
||||
/// 获取用户答题任务列表
|
||||
/// </summary>
|
||||
/// <param name="catalogId">二级目录Id(JournalCatalogId)</param>
|
||||
/// <param name="catalogId">二级目录Id(CatalogId)</param>
|
||||
/// <param name="userId">用户Id</param>
|
||||
/// <returns>答题任务列表</returns>
|
||||
Task<UserAnswerTaskListOutput> GetUserAnswerTaskListAsync(long catalogId, long userId);
|
||||
@ -42,18 +42,18 @@ public interface IUserAnswerTaskService : IBaseService<JournalPageTaskUserAnswer
|
||||
/// <summary>
|
||||
/// 获取跨页题目详情
|
||||
/// </summary>
|
||||
/// <param name="journalPageTaskId">任务Id</param>
|
||||
/// <param name="groupId">任务分组ID(JournalPageTaskGroupId)</param>
|
||||
/// <param name="userId">用户Id</param>
|
||||
/// <returns>跨页题目详情</returns>
|
||||
Task<CrossPageTaskOutput> GetCrossPageTaskAsync(long journalPageTaskId, long userId);
|
||||
Task<CrossPageTaskOutput> GetCrossPageTaskAsync(long groupId, long userId);
|
||||
|
||||
/// <summary>
|
||||
/// 领取任务积分
|
||||
/// </summary>
|
||||
/// <param name="journalPageTaskGroupId">任务分组Id(JournalPageTask.GroupId)</param>
|
||||
/// <param name="groupId">任务分组Id(JournalPageTask.GroupId)</param>
|
||||
/// <param name="userId">用户Id</param>
|
||||
/// <returns>领取结果</returns>
|
||||
Task<ClaimTaskPointsOutput> ClaimTaskPointsAsync(long journalPageTaskGroupId, long userId);
|
||||
Task<ClaimTaskPointsOutput> ClaimTaskPointsAsync(long groupId, long userId);
|
||||
|
||||
/// <summary>
|
||||
/// 批量领取任务积分(一键领取)
|
||||
@ -64,18 +64,10 @@ public interface IUserAnswerTaskService : IBaseService<JournalPageTaskUserAnswer
|
||||
Task<BatchClaimPointsOutput> BatchClaimPointsAsync(BatchClaimPointsInput input, long userId);
|
||||
|
||||
/// <summary>
|
||||
/// 获取杂志列表
|
||||
/// 获取目录树形结构(一级目录和二级目录的完整树形结构,包含任务详情)
|
||||
/// </summary>
|
||||
/// <param name="journalId">期刊Id</param>
|
||||
/// <param name="userId">用户Id</param>
|
||||
/// <param name="queryType">查询类型:Bound=已绑定,Unbound=未绑定(添加书籍用),默认是:Bound</param>
|
||||
/// <returns>杂志列表</returns>
|
||||
Task<List<JournalListOutput>> GetJournalListAsync(long userId, string? queryType);
|
||||
|
||||
/// <summary>
|
||||
/// 新增学生
|
||||
/// </summary>
|
||||
/// <param name="input">新增学生参数</param>
|
||||
/// <param name="wxUserId">当前登录用户微信UserId(WxUserId)</param>
|
||||
/// <returns>新增学生结果</returns>
|
||||
Task<bool> AddStudentAsync(AddStudentInput input, long wxUserId);
|
||||
/// <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>
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -52,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;
|
||||
}
|
||||
}
|
||||
@ -112,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;
|
||||
@ -130,7 +138,13 @@ namespace QYZH.InteractiveMagazine.Infrastructure.RabbitMQ
|
||||
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);
|
||||
|
||||
@ -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; } = [];
|
||||
}
|
||||
|
||||
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; } = [];
|
||||
}
|
||||
|
||||
@ -120,8 +120,35 @@ namespace QYZH.InteractiveMagazine.Models.Dto.Journal
|
||||
/// </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; }
|
||||
}
|
||||
}
|
||||
|
||||
@ -69,6 +69,16 @@ namespace QYZH.InteractiveMagazine.Models.Dto.Journal
|
||||
/// </summary>
|
||||
public string? PdfPreviewUrl { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:发布开始时间
|
||||
/// </summary>
|
||||
public DateTime? StartTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:发布结束时间
|
||||
/// </summary>
|
||||
public DateTime? EndTime { get; set; }
|
||||
|
||||
}
|
||||
public partial class JournalEditDto: JournalAddDto
|
||||
{
|
||||
|
||||
@ -29,5 +29,10 @@ namespace QYZH.InteractiveMagazine.Models.Dto.Journal
|
||||
/// </summary>
|
||||
public TaskBankTypeEnum Type { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否需要AI批改,默认需要
|
||||
/// </summary>
|
||||
public bool NeedAiProcess { get; set; } = true;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -18,7 +18,10 @@ namespace QYZH.InteractiveMagazine.Models.Dto.Journal
|
||||
/// 书页Id
|
||||
/// </summary>
|
||||
public long JournalPageId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 任务信息
|
||||
/// </summary>
|
||||
public string Task { get; set; }
|
||||
/// <summary>
|
||||
/// 题号
|
||||
/// </summary>
|
||||
@ -74,6 +77,11 @@ namespace QYZH.InteractiveMagazine.Models.Dto.Journal
|
||||
/// Prompt配置
|
||||
/// </summary>
|
||||
public string Prompt { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否需要AI批改
|
||||
/// </summary>
|
||||
public bool NeedAiProcess { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@ -44,7 +44,7 @@ namespace QYZH.InteractiveMagazine.Models.Dto.Journal
|
||||
/// <summary>
|
||||
/// 成长值
|
||||
/// </summary>
|
||||
public float GrowthPoint { get; set; }
|
||||
public int GrowthPoint { get; set; }
|
||||
/// <summary>
|
||||
/// 理解力评分
|
||||
/// </summary>
|
||||
@ -71,6 +71,11 @@ namespace QYZH.InteractiveMagazine.Models.Dto.Journal
|
||||
/// </summary>
|
||||
public string Prompt { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否需要AI批改,false时由人工批改
|
||||
/// </summary>
|
||||
public bool NeedAiProcess { get; set; } = true;
|
||||
|
||||
}
|
||||
|
||||
public class JournalPageTaskComplementInput
|
||||
|
||||
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;
|
||||
}
|
||||
@ -1,18 +0,0 @@
|
||||
namespace QYZH.InteractiveMagazine.Models.Dto.UserAnswerTaskService
|
||||
{
|
||||
/// <summary>
|
||||
/// 新增学生输入
|
||||
/// </summary>
|
||||
public class AddStudentInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 昵称
|
||||
/// </summary>
|
||||
public string? Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 头像地址
|
||||
/// </summary>
|
||||
public string? AvatarUrl { get; set; }
|
||||
}
|
||||
}
|
||||
@ -1,43 +0,0 @@
|
||||
namespace QYZH.InteractiveMagazine.Models.Dto.UserAnswerTaskService
|
||||
{
|
||||
/// <summary>
|
||||
/// 新增学生输出
|
||||
/// </summary>
|
||||
public class AddStudentOutput
|
||||
{
|
||||
/// <summary>
|
||||
/// 学生Id
|
||||
/// </summary>
|
||||
public long Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 关联微信用户Id
|
||||
/// </summary>
|
||||
public long WxUserId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 昵称
|
||||
/// </summary>
|
||||
public string? Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 头像地址
|
||||
/// </summary>
|
||||
public string? AvatarUrl { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 用户类型
|
||||
/// </summary>
|
||||
public string Type { get; set; } = "Normal";
|
||||
|
||||
/// <summary>
|
||||
/// 当前成长值
|
||||
/// </summary>
|
||||
public int GrowthPoints { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 积分余额
|
||||
/// </summary>
|
||||
public int Points { get; set; }
|
||||
}
|
||||
}
|
||||
@ -8,7 +8,7 @@ public class BatchClaimPointsInput
|
||||
/// <summary>
|
||||
/// 任务分组Id列表(JournalPageTaskGroupId)
|
||||
/// </summary>
|
||||
public List<long> GroupIds { get; set; } = [];
|
||||
public HashSet<long> GroupIds { get; set; } = [];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -26,21 +26,6 @@ public class BatchClaimPointsOutput
|
||||
/// </summary>
|
||||
public int FailCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 累计获得积分
|
||||
/// </summary>
|
||||
public int TotalPoints { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 累计获得成长值
|
||||
/// </summary>
|
||||
public int TotalGrowthPoints { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 领取后积分余额
|
||||
/// </summary>
|
||||
public int BalanceAfter { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 成功领取详情列表
|
||||
/// </summary>
|
||||
@ -58,14 +43,9 @@ public class BatchClaimPointsOutput
|
||||
public class BatchClaimSuccessItem
|
||||
{
|
||||
/// <summary>
|
||||
/// 任务分组Id
|
||||
/// 任务编号(从 JournalPageTask.No 字段按 - 分割取索引2,前面加"任务")
|
||||
/// </summary>
|
||||
public long GroupId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 任务名称
|
||||
/// </summary>
|
||||
public string TaskName { get; set; }
|
||||
public string TaskNo { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 积分值
|
||||
@ -78,7 +58,7 @@ public class BatchClaimSuccessItem
|
||||
public int GrowthPoints { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 题目得分(从 JournalPageTaskUserAnswer 表获取,该 GroupId 下所有相关任务得分之和)
|
||||
/// 题目得分
|
||||
/// </summary>
|
||||
public float Score { get; set; }
|
||||
}
|
||||
@ -93,6 +73,11 @@ public class BatchClaimFailItem
|
||||
/// </summary>
|
||||
public long GroupId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 任务编号(从 JournalPageTask.No 字段按 - 分割取索引2,前面加"任务")
|
||||
/// </summary>
|
||||
public string TaskNo { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 失败原因
|
||||
/// </summary>
|
||||
|
||||
@ -28,5 +28,5 @@ public class CatalogListOutput
|
||||
/// <summary>
|
||||
/// 二级栏目列表
|
||||
/// </summary>
|
||||
public List<SecondCatalogOutput> Children { get; set; } = new();
|
||||
public List<SecondCatalogOutput> Children { get; set; } = [];
|
||||
}
|
||||
@ -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; }
|
||||
}
|
||||
@ -6,32 +6,22 @@ namespace QYZH.InteractiveMagazine.Models.Dto.UserAnswerTaskService;
|
||||
public class ClaimTaskPointsOutput
|
||||
{
|
||||
/// <summary>
|
||||
/// 积分流水记录Id
|
||||
/// 总成长值(主任务的 GrowthPoint)
|
||||
/// </summary>
|
||||
public long RecordId { get; set; }
|
||||
public int TotalGrowthPoints { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 操作前积分余额
|
||||
/// 总积分(主任务的 Points)
|
||||
/// </summary>
|
||||
public int PreviousBalance { get; set; }
|
||||
public int TotalPoints { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 操作后积分余额
|
||||
/// </summary>
|
||||
public int NewBalance { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 实际增加数量
|
||||
/// </summary>
|
||||
public int AddedAmount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 任务名称
|
||||
/// </summary>
|
||||
public string TaskName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 题目得分(从 JournalPageTaskUserAnswer 表获取,跨页题目为所有相关任务得分之和)
|
||||
/// 得分(从 JournalPageTaskUserAnswer 表获取,JournalPageTaskId == GroupId 的 Score)
|
||||
/// </summary>
|
||||
public float Score { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 任务编号(从 JournalPageTask.No 字段按 - 分割取索引2,前面加"任务")
|
||||
/// </summary>
|
||||
public string TaskNo { get; set; }
|
||||
}
|
||||
|
||||
@ -23,10 +23,20 @@ public class CrossPageTaskOutput
|
||||
/// <summary>
|
||||
/// 图片数组(已答题用PageAnswerUrl,未答题用TaskUrl)
|
||||
/// </summary>
|
||||
public List<string> Images { get; set; } = new();
|
||||
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; }
|
||||
}
|
||||
|
||||
@ -1,32 +0,0 @@
|
||||
namespace QYZH.InteractiveMagazine.Models.Dto.UserAnswerTaskService;
|
||||
|
||||
/// <summary>
|
||||
/// 杂志列表输出
|
||||
/// </summary>
|
||||
public class JournalListOutput
|
||||
{
|
||||
/// <summary>
|
||||
/// 杂志Id
|
||||
/// </summary>
|
||||
public long Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 杂志名称
|
||||
/// </summary>
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 杂志标题
|
||||
/// </summary>
|
||||
public string Title { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 封面
|
||||
/// </summary>
|
||||
public string Cover { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否已绑定
|
||||
/// </summary>
|
||||
public bool IsBound { get; set; }
|
||||
}
|
||||
@ -26,24 +26,25 @@ public class UserAnswerTaskListOutput
|
||||
public int EndPage { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 任务名称列表
|
||||
/// 任务列表
|
||||
/// </summary>
|
||||
public List<string> TaskNames { get; set; } = new();
|
||||
public List<UserAnswerTaskItemOutput> Tasks { get; set; } = new();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 用户答题任务项输出
|
||||
/// </summary>
|
||||
public class UserAnswerTaskItemOutput
|
||||
{
|
||||
/// <summary>
|
||||
/// 任务GroupId
|
||||
/// </summary>
|
||||
public long GroupId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 任务状态(进行中、已完成、未开始)
|
||||
/// 任务名称
|
||||
/// </summary>
|
||||
public string Status { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 答题时间(分钟)
|
||||
/// </summary>
|
||||
public double AnswerMinutes { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 徽章个数
|
||||
/// </summary>
|
||||
public int MedalCount { get; set; }
|
||||
public string TaskName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 总成长值
|
||||
@ -54,4 +55,14 @@ public class UserAnswerTaskListOutput
|
||||
/// 总积分
|
||||
/// </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>
|
||||
|
||||
@ -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("CommunityMessageLike")]
|
||||
[SugarTable("Community_MessageLike")]
|
||||
public partial class CommunityMessageLike : SqlSugarBaseEntity
|
||||
{
|
||||
public CommunityMessageLike()
|
||||
|
||||
@ -6,7 +6,7 @@ namespace QYZH.InteractiveMagazine.Models.Entity
|
||||
///<summary>
|
||||
///固定模板言论表
|
||||
///</summary>
|
||||
[SugarTable("CommunityTemplateSentence")]
|
||||
[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(){
|
||||
@ -106,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,23 +1,14 @@
|
||||
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:
|
||||
@ -64,7 +55,7 @@ namespace QYZH.InteractiveMagazine.Models.Entity
|
||||
/// Default:0
|
||||
/// Nullable:True
|
||||
/// </summary>
|
||||
public int? GrowthPoint { get; set; }
|
||||
public int GrowthPoint { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:积分设置
|
||||
|
||||
@ -8,7 +8,7 @@ namespace QYZH.InteractiveMagazine.Models.Entity
|
||||
///<summary>
|
||||
///杂志任务答案表;
|
||||
///</summary>
|
||||
[SugarTable("JournalPageTaskAnswer")]
|
||||
[SugarTable("Journal_PageTaskAnswer")]
|
||||
public partial class JournalPageTaskAnswer : SqlSugarBaseEntity
|
||||
{
|
||||
public JournalPageTaskAnswer(){
|
||||
|
||||
@ -1,215 +1,236 @@
|
||||
using QYZH.InteractiveMagazine.Models.Enum;
|
||||
using SqlSugar;
|
||||
|
||||
namespace QYZH.InteractiveMagazine.Models.Entity
|
||||
{
|
||||
///<summary>
|
||||
///杂志任务用户作答记录;
|
||||
///</summary>
|
||||
[SugarTable("JournalPageTaskUserAnswer")]
|
||||
/// <summary>
|
||||
/// 杂志任务用户作答记录
|
||||
/// </summary>
|
||||
[SugarTable("Journal_PageTaskUserAnswer")]
|
||||
public class JournalPageTaskUserAnswer : SqlSugarBaseEntity
|
||||
{
|
||||
/// <summary>
|
||||
/// Desc:书id
|
||||
/// Default:
|
||||
/// Nullable:False
|
||||
/// </summary>
|
||||
/// </summary>
|
||||
public long JournalId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:书页id
|
||||
/// Default:
|
||||
/// Nullable:False
|
||||
/// </summary>
|
||||
/// </summary>
|
||||
public long JournalPageId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:任务id
|
||||
/// Default:
|
||||
/// Nullable:False
|
||||
/// </summary>
|
||||
/// </summary>
|
||||
public long JournalPageTaskId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:任务分组id
|
||||
/// Default:
|
||||
/// Nullable:False
|
||||
/// </summary>
|
||||
/// </summary>
|
||||
public long JournalPageTaskGroupId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:用户id
|
||||
/// Default:
|
||||
/// Nullable:False
|
||||
/// </summary>
|
||||
/// </summary>
|
||||
public long UserId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:作答结果
|
||||
/// Default:
|
||||
/// Nullable:True
|
||||
/// </summary>
|
||||
/// </summary>
|
||||
public string Result { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:积分
|
||||
/// Default:0
|
||||
/// Nullable:False
|
||||
/// </summary>
|
||||
public float Points { get; set; }
|
||||
/// </summary>
|
||||
public int Points { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:成长值
|
||||
/// Default:0
|
||||
/// Nullable:False
|
||||
/// </summary>
|
||||
public float GrowthPoints { get; set; }
|
||||
/// </summary>
|
||||
public int GrowthPoints { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:题目得分
|
||||
/// Default:0
|
||||
/// Nullable:False
|
||||
/// </summary>
|
||||
/// </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>
|
||||
/// </summary>
|
||||
public string QuestionAnswerUrl { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:答案图片地址
|
||||
/// Default:
|
||||
/// Nullable:True
|
||||
/// </summary>
|
||||
/// </summary>
|
||||
public string AnswerUrl { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:页面作答图片地址
|
||||
/// Default:
|
||||
/// Nullable:True
|
||||
/// </summary>
|
||||
/// </summary>
|
||||
public string PageAnswerUrl { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:乐观锁
|
||||
/// Default:
|
||||
/// Nullable:False
|
||||
/// </summary>
|
||||
/// </summary>
|
||||
public int Revision { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:作答状态
|
||||
/// Default:
|
||||
/// Nullable:False
|
||||
/// </summary>
|
||||
public int AnswerStatus { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:作答开始时间
|
||||
/// Default:
|
||||
/// Nullable:True
|
||||
/// </summary>
|
||||
/// </summary>
|
||||
public DateTime AnswerStartTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:作答结束时间
|
||||
/// Default:
|
||||
/// Nullable:True
|
||||
/// </summary>
|
||||
/// </summary>
|
||||
public DateTime AnswerEndTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:作答耗时(秒)
|
||||
/// Default:0
|
||||
/// Nullable:False
|
||||
/// </summary>
|
||||
/// </summary>
|
||||
public int AnswerSeconds { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:图像识别结果
|
||||
/// Default:
|
||||
/// Nullable:False
|
||||
/// </summary>
|
||||
/// </summary>
|
||||
public int ImageRecognition { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:书页序号
|
||||
/// Default:
|
||||
/// Nullable:False
|
||||
/// </summary>
|
||||
/// </summary>
|
||||
public int JournalPageNum { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:修改次数
|
||||
/// Default:
|
||||
/// Nullable:False
|
||||
/// </summary>
|
||||
/// </summary>
|
||||
public int Modify { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:最后标签id
|
||||
/// Default:
|
||||
/// Nullable:False
|
||||
/// </summary>
|
||||
/// </summary>
|
||||
public long LastTag { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:点读书页序号
|
||||
/// Default:
|
||||
/// Nullable:False
|
||||
/// </summary>
|
||||
/// </summary>
|
||||
public int DotPageNum { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:页面结果图片地址
|
||||
/// Default:
|
||||
/// Nullable:True
|
||||
/// </summary>
|
||||
/// </summary>
|
||||
public string PageResultUrl { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:题型
|
||||
/// Default:
|
||||
/// Nullable:True
|
||||
/// </summary>
|
||||
/// </summary>
|
||||
public string Type { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:点读书页编号
|
||||
/// Default:
|
||||
/// Nullable:True
|
||||
/// </summary>
|
||||
/// </summary>
|
||||
public string DotPageNo { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:页面作答点读图片地址
|
||||
/// Default:
|
||||
/// Nullable:True
|
||||
/// </summary>
|
||||
/// </summary>
|
||||
public string PageAnswerDotUrl { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:中断次数
|
||||
/// Default:0
|
||||
/// Nullable:False
|
||||
/// </summary>
|
||||
/// </summary>
|
||||
public int BreakCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:中断时间记录
|
||||
/// Default:
|
||||
/// Nullable:True
|
||||
/// </summary>
|
||||
/// </summary>
|
||||
public string BreakTimes { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:作业状态
|
||||
/// Default:
|
||||
/// Nullable:True
|
||||
/// </summary>
|
||||
public string AssignmentStatus { get; set; }
|
||||
|
||||
/// </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(){
|
||||
|
||||
@ -6,7 +6,7 @@ namespace QYZH.InteractiveMagazine.Models.Entity
|
||||
/// <summary>
|
||||
/// 宠物皮肤定义表
|
||||
/// </summary>
|
||||
[SugarTable("PetSkin")]
|
||||
[SugarTable("Pet_Skin")]
|
||||
public partial class PetSkin : SqlSugarBaseEntity
|
||||
{
|
||||
public PetSkin() { }
|
||||
|
||||
@ -6,7 +6,7 @@ namespace QYZH.InteractiveMagazine.Models.Entity
|
||||
/// <summary>
|
||||
/// 宠物皮肤图片表(每个皮肤在每个进化阶段下有一组有序图片,用于动态表示)
|
||||
/// </summary>
|
||||
[SugarTable("PetSkinImage")]
|
||||
[SugarTable("Pet_SkinImage")]
|
||||
public partial class PetSkinImage : SqlSugarBaseEntity
|
||||
{
|
||||
public PetSkinImage() { }
|
||||
|
||||
@ -6,7 +6,7 @@ namespace QYZH.InteractiveMagazine.Models.Entity
|
||||
/// <summary>
|
||||
/// 宠物模板表(定义不同宠物种类,支持后期多宠物扩展)
|
||||
/// </summary>
|
||||
[SugarTable("PetTemplate")]
|
||||
[SugarTable("Pet_Template")]
|
||||
public partial class PetTemplate : SqlSugarBaseEntity
|
||||
{
|
||||
public PetTemplate() { }
|
||||
|
||||
@ -6,7 +6,7 @@ namespace QYZH.InteractiveMagazine.Models.Entity
|
||||
///<summary>
|
||||
///积分流水记录表
|
||||
///</summary>
|
||||
[SugarTable("PointsRecord")]
|
||||
[SugarTable("Points_Record")]
|
||||
public partial class PointsRecord : SqlSugarBaseEntity
|
||||
{
|
||||
public PointsRecord(){
|
||||
|
||||
@ -6,7 +6,7 @@ namespace QYZH.InteractiveMagazine.Models.Entity
|
||||
///<summary>
|
||||
///虚拟商品表
|
||||
///</summary>
|
||||
[SugarTable("Product")]
|
||||
[SugarTable("Mall_Product")]
|
||||
public partial class Product : SqlSugarBaseEntity
|
||||
{
|
||||
public Product()
|
||||
|
||||
25
QYZH.InteractiveMagazine.Models/Entity/UploadDomain.cs
Normal file
25
QYZH.InteractiveMagazine.Models/Entity/UploadDomain.cs
Normal file
@ -0,0 +1,25 @@
|
||||
using SqlSugar;
|
||||
|
||||
namespace QYZH.InteractiveMagazine.Models.Entity
|
||||
{
|
||||
/// <summary>
|
||||
/// 上传地址管理表
|
||||
/// </summary>
|
||||
[SugarTable("System_UploadDomain")]
|
||||
public partial class UploadDomain : SqlSugarBaseEntity
|
||||
{
|
||||
/// <summary>
|
||||
/// Desc:上传地址
|
||||
/// Default:
|
||||
/// Nullable:False
|
||||
/// </summary>
|
||||
public string Domain { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Desc:已分配人数
|
||||
/// Default:0
|
||||
/// Nullable:False
|
||||
/// </summary>
|
||||
public int AssignedCount { get; set; }
|
||||
}
|
||||
}
|
||||
@ -6,7 +6,7 @@ namespace QYZH.InteractiveMagazine.Models.Entity
|
||||
///<summary>
|
||||
///用户背包表
|
||||
///</summary>
|
||||
[SugarTable("UserBag")]
|
||||
[SugarTable("User_Bag")]
|
||||
public partial class UserBag : SqlSugarBaseEntity
|
||||
{
|
||||
public UserBag(){
|
||||
|
||||
@ -6,7 +6,7 @@ namespace QYZH.InteractiveMagazine.Models.Entity
|
||||
///<summary>
|
||||
///用户与期刊关联表
|
||||
///</summary>
|
||||
[SugarTable("UserJournal")]
|
||||
[SugarTable("User_Journal")]
|
||||
public partial class UserJournal : SqlSugarBaseEntity
|
||||
{
|
||||
public UserJournal()
|
||||
@ -20,7 +20,7 @@ namespace QYZH.InteractiveMagazine.Models.Entity
|
||||
/// Nullable:False
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnName = "UserId")]
|
||||
public long UserId { get; set; }
|
||||
public long? UserId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:期刊模板Id(对应Journal表的期刊定义)
|
||||
@ -31,12 +31,18 @@ namespace QYZH.InteractiveMagazine.Models.Entity
|
||||
public long JournalId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:关联类型: Read(已读), Favorite(收藏), Subscribe(订阅)
|
||||
/// Default:Read
|
||||
/// Desc:关联类型: 默认0
|
||||
/// Nullable:False
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnName = "Type")]
|
||||
public UserJournalTypeEnum Type { get; set; }
|
||||
public int Type { get; set; } = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Desc:二维码地址
|
||||
/// Default:
|
||||
/// Nullable:True
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnName = "QrCodeUrl")]
|
||||
public string? QrCodeUrl { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,7 +6,7 @@ namespace QYZH.InteractiveMagazine.Models.Entity
|
||||
///<summary>
|
||||
///用户勋章表
|
||||
///</summary>
|
||||
[SugarTable("UserMedal")]
|
||||
[SugarTable("User_Medal")]
|
||||
public partial class UserMedal : SqlSugarBaseEntity
|
||||
{
|
||||
public UserMedal(){
|
||||
|
||||
@ -6,7 +6,7 @@ namespace QYZH.InteractiveMagazine.Models.Entity
|
||||
///<summary>
|
||||
///用户宠物实例表
|
||||
///</summary>
|
||||
[SugarTable("UserPet")]
|
||||
[SugarTable("User_Pet")]
|
||||
public partial class UserPet : SqlSugarBaseEntity
|
||||
{
|
||||
public UserPet()
|
||||
|
||||
@ -6,7 +6,7 @@ namespace QYZH.InteractiveMagazine.Models.Entity
|
||||
///<summary>
|
||||
///用户表 — 游戏/角色数据,关联 WxUser
|
||||
///</summary>
|
||||
[SugarTable("Users")]
|
||||
[SugarTable("User_Account")]
|
||||
public partial class Users : SqlSugarBaseEntity
|
||||
{
|
||||
/// <summary>
|
||||
@ -57,5 +57,12 @@ namespace QYZH.InteractiveMagazine.Models.Entity
|
||||
/// Nullable:False
|
||||
/// </summary>
|
||||
public bool IsLastOnline { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:用户作答信息上传地址
|
||||
/// Default:
|
||||
/// Nullable:False
|
||||
/// </summary>
|
||||
public string UploadDomain { get; set; } = string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,7 +6,7 @@ namespace QYZH.InteractiveMagazine.Models.Entity
|
||||
/// 微信用户表 — 存储微信身份信息和基本资料
|
||||
/// 一个 WxUser 可关联多个 Users(多角色/多用户)
|
||||
/// </summary>
|
||||
[SugarTable("WxUser")]
|
||||
[SugarTable("User_WechatAccount")]
|
||||
public partial class WxUser : SqlSugarBaseEntity
|
||||
{
|
||||
public WxUser() { }
|
||||
|
||||
21
QYZH.InteractiveMagazine.Models/Enum/AdminRoleTypeEnum.cs
Normal file
21
QYZH.InteractiveMagazine.Models/Enum/AdminRoleTypeEnum.cs
Normal file
@ -0,0 +1,21 @@
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace QYZH.InteractiveMagazine.Models.Enum;
|
||||
|
||||
/// <summary>
|
||||
/// 后台角色类型枚举
|
||||
/// </summary>
|
||||
public enum AdminRoleTypeEnum
|
||||
{
|
||||
/// <summary>
|
||||
/// 普通角色
|
||||
/// </summary>
|
||||
[Description("普通角色")]
|
||||
Normal = 0,
|
||||
|
||||
/// <summary>
|
||||
/// 系统角色
|
||||
/// </summary>
|
||||
[Description("系统角色")]
|
||||
System = 1
|
||||
}
|
||||
22
QYZH.InteractiveMagazine.Models/Enum/AssignmentStatusEnum.cs
Normal file
22
QYZH.InteractiveMagazine.Models/Enum/AssignmentStatusEnum.cs
Normal file
@ -0,0 +1,22 @@
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace QYZH.InteractiveMagazine.Models.Enum
|
||||
{
|
||||
/// <summary>
|
||||
/// 作业分配审核状态枚举: 0-未审核 1-已审核
|
||||
/// </summary>
|
||||
public enum AssignmentStatusEnum
|
||||
{
|
||||
/// <summary>
|
||||
/// 未审核
|
||||
/// </summary>
|
||||
[Description("未审核")]
|
||||
UnAssignmented = 0,
|
||||
|
||||
/// <summary>
|
||||
/// 已审核
|
||||
/// </summary>
|
||||
[Description("已审核")]
|
||||
Assignmented = 1
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user