Compare commits
11 Commits
1d467a5fc4
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| cbdee5068a | |||
| 685a8aeaec | |||
| 766be485d0 | |||
| faf40d6e27 | |||
| 57e40440ab | |||
| 4bb03aa0df | |||
| de9a0c2978 | |||
| 195792ec66 | |||
| a8b19cbacc | |||
| a64d1b459a | |||
| f7f505d109 |
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
|
||||
@ -4,122 +4,122 @@ 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 `AdminMenu` WHERE `Code` = 'ai-config' LIMIT 1), (@shortSnowflakeTimestamp * 1000 + (@shortSnowflakeSequence := @shortSnowflakeSequence + 1)));
|
||||
INSERT INTO `AdminMenu` (`Id`, `ParentId`, `Name`, `Code`, `Path`, `Component`, `Icon`, `Sort`, `IsVisible`, `Status`, `IsDeleted`, `CreatedBy`, `CreatedAt`, `UpdatedBy`, `UpdatedAt`)
|
||||
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 `AdminMenu` WHERE `Code` = 'ai-config:base-prompt' LIMIT 1), (@shortSnowflakeTimestamp * 1000 + (@shortSnowflakeSequence := @shortSnowflakeSequence + 1)));
|
||||
INSERT INTO `AdminMenu` (`Id`, `ParentId`, `Name`, `Code`, `Path`, `Component`, `Icon`, `Sort`, `IsVisible`, `Status`, `IsDeleted`, `CreatedBy`, `CreatedAt`, `UpdatedBy`, `UpdatedAt`)
|
||||
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 `AdminMenu` WHERE `Code` = 'main-nav' LIMIT 1), (@shortSnowflakeTimestamp * 1000 + (@shortSnowflakeSequence := @shortSnowflakeSequence + 1)));
|
||||
INSERT INTO `AdminMenu` (`Id`, `ParentId`, `Name`, `Code`, `Path`, `Component`, `Icon`, `Sort`, `IsVisible`, `Status`, `IsDeleted`, `CreatedBy`, `CreatedAt`, `UpdatedBy`, `UpdatedAt`)
|
||||
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 `AdminMenu` WHERE `Code` = 'main-nav:home' LIMIT 1), (@shortSnowflakeTimestamp * 1000 + (@shortSnowflakeSequence := @shortSnowflakeSequence + 1)));
|
||||
INSERT INTO `AdminMenu` (`Id`, `ParentId`, `Name`, `Code`, `Path`, `Component`, `Icon`, `Sort`, `IsVisible`, `Status`, `IsDeleted`, `CreatedBy`, `CreatedAt`, `UpdatedBy`, `UpdatedAt`)
|
||||
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 `AdminMenu` WHERE `Code` = 'main-nav:journal-qr-code' LIMIT 1), (@shortSnowflakeTimestamp * 1000 + (@shortSnowflakeSequence := @shortSnowflakeSequence + 1)));
|
||||
INSERT INTO `AdminMenu` (`Id`, `ParentId`, `Name`, `Code`, `Path`, `Component`, `Icon`, `Sort`, `IsVisible`, `Status`, `IsDeleted`, `CreatedBy`, `CreatedAt`, `UpdatedBy`, `UpdatedAt`)
|
||||
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 `AdminMenu` WHERE `Code` = 'main-nav:book-management' LIMIT 1), (@shortSnowflakeTimestamp * 1000 + (@shortSnowflakeSequence := @shortSnowflakeSequence + 1)));
|
||||
INSERT INTO `AdminMenu` (`Id`, `ParentId`, `Name`, `Code`, `Path`, `Component`, `Icon`, `Sort`, `IsVisible`, `Status`, `IsDeleted`, `CreatedBy`, `CreatedAt`, `UpdatedBy`, `UpdatedAt`)
|
||||
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 `AdminMenu` WHERE `Code` = 'main-nav:community-message-management' LIMIT 1), (@shortSnowflakeTimestamp * 1000 + (@shortSnowflakeSequence := @shortSnowflakeSequence + 1)));
|
||||
INSERT INTO `AdminMenu` (`Id`, `ParentId`, `Name`, `Code`, `Path`, `Component`, `Icon`, `Sort`, `IsVisible`, `Status`, `IsDeleted`, `CreatedBy`, `CreatedAt`, `UpdatedBy`, `UpdatedAt`)
|
||||
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 `AdminMenu` WHERE `Code` = 'main-nav:medal-management' LIMIT 1), (@shortSnowflakeTimestamp * 1000 + (@shortSnowflakeSequence := @shortSnowflakeSequence + 1)));
|
||||
INSERT INTO `AdminMenu` (`Id`, `ParentId`, `Name`, `Code`, `Path`, `Component`, `Icon`, `Sort`, `IsVisible`, `Status`, `IsDeleted`, `CreatedBy`, `CreatedAt`, `UpdatedBy`, `UpdatedAt`)
|
||||
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 `AdminMenu` WHERE `Code` = 'main-nav:pet-management' LIMIT 1), (@shortSnowflakeTimestamp * 1000 + (@shortSnowflakeSequence := @shortSnowflakeSequence + 1)));
|
||||
INSERT INTO `AdminMenu` (`Id`, `ParentId`, `Name`, `Code`, `Path`, `Component`, `Icon`, `Sort`, `IsVisible`, `Status`, `IsDeleted`, `CreatedBy`, `CreatedAt`, `UpdatedBy`, `UpdatedAt`)
|
||||
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 `AdminMenu` WHERE `Code` = 'main-nav:product-management' LIMIT 1), (@shortSnowflakeTimestamp * 1000 + (@shortSnowflakeSequence := @shortSnowflakeSequence + 1)));
|
||||
INSERT INTO `AdminMenu` (`Id`, `ParentId`, `Name`, `Code`, `Path`, `Component`, `Icon`, `Sort`, `IsVisible`, `Status`, `IsDeleted`, `CreatedBy`, `CreatedAt`, `UpdatedBy`, `UpdatedAt`)
|
||||
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 `AdminMenu` WHERE `Code` = 'main-nav:check-in-config-management' LIMIT 1), (@shortSnowflakeTimestamp * 1000 + (@shortSnowflakeSequence := @shortSnowflakeSequence + 1)));
|
||||
INSERT INTO `AdminMenu` (`Id`, `ParentId`, `Name`, `Code`, `Path`, `Component`, `Icon`, `Sort`, `IsVisible`, `Status`, `IsDeleted`, `CreatedBy`, `CreatedAt`, `UpdatedBy`, `UpdatedAt`)
|
||||
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 `AdminMenu` WHERE `Code` = 'data-statistics' LIMIT 1), (@shortSnowflakeTimestamp * 1000 + (@shortSnowflakeSequence := @shortSnowflakeSequence + 1)));
|
||||
INSERT INTO `AdminMenu` (`Id`, `ParentId`, `Name`, `Code`, `Path`, `Component`, `Icon`, `Sort`, `IsVisible`, `Status`, `IsDeleted`, `CreatedBy`, `CreatedAt`, `UpdatedBy`, `UpdatedAt`)
|
||||
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 `AdminMenu` WHERE `Code` = 'data-statistics:statistics' LIMIT 1), (@shortSnowflakeTimestamp * 1000 + (@shortSnowflakeSequence := @shortSnowflakeSequence + 1)));
|
||||
INSERT INTO `AdminMenu` (`Id`, `ParentId`, `Name`, `Code`, `Path`, `Component`, `Icon`, `Sort`, `IsVisible`, `Status`, `IsDeleted`, `CreatedBy`, `CreatedAt`, `UpdatedBy`, `UpdatedAt`)
|
||||
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 `AdminMenu` WHERE `Code` = 'system-manage' LIMIT 1), (@shortSnowflakeTimestamp * 1000 + (@shortSnowflakeSequence := @shortSnowflakeSequence + 1)));
|
||||
INSERT INTO `AdminMenu` (`Id`, `ParentId`, `Name`, `Code`, `Path`, `Component`, `Icon`, `Sort`, `IsVisible`, `Status`, `IsDeleted`, `CreatedBy`, `CreatedAt`, `UpdatedBy`, `UpdatedAt`)
|
||||
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 `AdminMenu` WHERE `Code` = 'system-manage:menu' LIMIT 1), (@shortSnowflakeTimestamp * 1000 + (@shortSnowflakeSequence := @shortSnowflakeSequence + 1)));
|
||||
INSERT INTO `AdminMenu` (`Id`, `ParentId`, `Name`, `Code`, `Path`, `Component`, `Icon`, `Sort`, `IsVisible`, `Status`, `IsDeleted`, `CreatedBy`, `CreatedAt`, `UpdatedBy`, `UpdatedAt`)
|
||||
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 `AdminMenu` WHERE `Code` = 'system-manage:operation-log' LIMIT 1), (@shortSnowflakeTimestamp * 1000 + (@shortSnowflakeSequence := @shortSnowflakeSequence + 1)));
|
||||
INSERT INTO `AdminMenu` (`Id`, `ParentId`, `Name`, `Code`, `Path`, `Component`, `Icon`, `Sort`, `IsVisible`, `Status`, `IsDeleted`, `CreatedBy`, `CreatedAt`, `UpdatedBy`, `UpdatedAt`)
|
||||
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 `AdminMenu` WHERE `Code` = 'system-manage:user' LIMIT 1), (@shortSnowflakeTimestamp * 1000 + (@shortSnowflakeSequence := @shortSnowflakeSequence + 1)));
|
||||
INSERT INTO `AdminMenu` (`Id`, `ParentId`, `Name`, `Code`, `Path`, `Component`, `Icon`, `Sort`, `IsVisible`, `Status`, `IsDeleted`, `CreatedBy`, `CreatedAt`, `UpdatedBy`, `UpdatedAt`)
|
||||
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 `AdminMenu` WHERE `Code` = 'system-manage:user-management' LIMIT 1), (@shortSnowflakeTimestamp * 1000 + (@shortSnowflakeSequence := @shortSnowflakeSequence + 1)));
|
||||
INSERT INTO `AdminMenu` (`Id`, `ParentId`, `Name`, `Code`, `Path`, `Component`, `Icon`, `Sort`, `IsVisible`, `Status`, `IsDeleted`, `CreatedBy`, `CreatedAt`, `UpdatedBy`, `UpdatedAt`)
|
||||
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 `AdminMenu` WHERE `Code` = 'help-center' LIMIT 1), (@shortSnowflakeTimestamp * 1000 + (@shortSnowflakeSequence := @shortSnowflakeSequence + 1)));
|
||||
INSERT INTO `AdminMenu` (`Id`, `ParentId`, `Name`, `Code`, `Path`, `Component`, `Icon`, `Sort`, `IsVisible`, `Status`, `IsDeleted`, `CreatedBy`, `CreatedAt`, `UpdatedBy`, `UpdatedAt`)
|
||||
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 `AdminMenu` WHERE `Code` = 'help-center:contact-support' LIMIT 1), (@shortSnowflakeTimestamp * 1000 + (@shortSnowflakeSequence := @shortSnowflakeSequence + 1)));
|
||||
INSERT INTO `AdminMenu` (`Id`, `ParentId`, `Name`, `Code`, `Path`, `Component`, `Icon`, `Sort`, `IsVisible`, `Status`, `IsDeleted`, `CreatedBy`, `CreatedAt`, `UpdatedBy`, `UpdatedAt`)
|
||||
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 `AdminMenu` WHERE `Code` = 'help-center:usage-help' LIMIT 1), (@shortSnowflakeTimestamp * 1000 + (@shortSnowflakeSequence := @shortSnowflakeSequence + 1)));
|
||||
INSERT INTO `AdminMenu` (`Id`, `ParentId`, `Name`, `Code`, `Path`, `Component`, `Icon`, `Sort`, `IsVisible`, `Status`, `IsDeleted`, `CreatedBy`, `CreatedAt`, `UpdatedBy`, `UpdatedAt`)
|
||||
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 `AdminMenu` WHERE `Code` = 'pen-management' LIMIT 1), (@shortSnowflakeTimestamp * 1000 + (@shortSnowflakeSequence := @shortSnowflakeSequence + 1)));
|
||||
INSERT INTO `AdminMenu` (`Id`, `ParentId`, `Name`, `Code`, `Path`, `Component`, `Icon`, `Sort`, `IsVisible`, `Status`, `IsDeleted`, `CreatedBy`, `CreatedAt`, `UpdatedBy`, `UpdatedAt`)
|
||||
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 `AdminMenu` WHERE `Code` = 'pen-management:list' LIMIT 1), (@shortSnowflakeTimestamp * 1000 + (@shortSnowflakeSequence := @shortSnowflakeSequence + 1)));
|
||||
INSERT INTO `AdminMenu` (`Id`, `ParentId`, `Name`, `Code`, `Path`, `Component`, `Icon`, `Sort`, `IsVisible`, `Status`, `IsDeleted`, `CreatedBy`, `CreatedAt`, `UpdatedBy`, `UpdatedAt`)
|
||||
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 `AdminMenu` WHERE `Code` = 'pen-management:coordinate-data' LIMIT 1), (@shortSnowflakeTimestamp * 1000 + (@shortSnowflakeSequence := @shortSnowflakeSequence + 1)));
|
||||
INSERT INTO `AdminMenu` (`Id`, `ParentId`, `Name`, `Code`, `Path`, `Component`, `Icon`, `Sort`, `IsVisible`, `Status`, `IsDeleted`, `CreatedBy`, `CreatedAt`, `UpdatedBy`, `UpdatedAt`)
|
||||
SET @pen_management_coordinate_data_id = COALESCE((SELECT `Id` FROM `System_AdminMenu` WHERE `Code` = 'pen-management:coordinate-data' LIMIT 1), (@shortSnowflakeTimestamp * 1000 + (@shortSnowflakeSequence := @shortSnowflakeSequence + 1)));
|
||||
INSERT INTO `System_AdminMenu` (`Id`, `ParentId`, `Name`, `Code`, `Path`, `Component`, `Icon`, `Sort`, `IsVisible`, `Status`, `IsDeleted`, `CreatedBy`, `CreatedAt`, `UpdatedBy`, `UpdatedAt`)
|
||||
VALUES (@pen_management_coordinate_data_id, @pen_management_id, '点阵笔坐标数据', 'pen-management:coordinate-data', '/pen-management/coordinate-data', 'view.pen-management_coordinate-data', 'ic:baseline-edit', 1, b'1', 1, b'0', @createdBy, @now, @createdBy, @now)
|
||||
ON DUPLICATE KEY UPDATE `ParentId` = VALUES(`ParentId`), `Name` = VALUES(`Name`), `Path` = VALUES(`Path`), `Component` = VALUES(`Component`), `Icon` = VALUES(`Icon`), `Sort` = VALUES(`Sort`), `IsVisible` = VALUES(`IsVisible`), `Status` = VALUES(`Status`), `IsDeleted` = b'0', `UpdatedBy` = @createdBy, `UpdatedAt` = @now;
|
||||
|
||||
22
DatabaseScripts/message_outbox.sql
Normal file
22
DatabaseScripts/message_outbox.sql
Normal file
@ -0,0 +1,22 @@
|
||||
CREATE TABLE IF NOT EXISTS `Message_Outbox` (
|
||||
`Id` bigint NOT NULL,
|
||||
`Exchange` varchar(200) NOT NULL,
|
||||
`Queue` varchar(200) NOT NULL,
|
||||
`RoutingKey` varchar(200) NOT NULL,
|
||||
`Payload` json NOT NULL,
|
||||
`RetryCount` int NOT NULL DEFAULT 0,
|
||||
`NextRetryAt` datetime NULL,
|
||||
`SentAt` datetime NULL,
|
||||
`LastError` varchar(2000) NULL,
|
||||
`BusinessType` varchar(100) NOT NULL,
|
||||
`BusinessId` bigint NOT NULL,
|
||||
`Status` int NOT NULL DEFAULT 0,
|
||||
`IsDeleted` bit NOT NULL DEFAULT b'0',
|
||||
`CreatedBy` varchar(100) NOT NULL,
|
||||
`CreatedAt` datetime NOT NULL,
|
||||
`UpdatedBy` varchar(100) NULL,
|
||||
`UpdatedAt` datetime NULL,
|
||||
PRIMARY KEY (`Id`),
|
||||
KEY `idx_message_outbox_status_next_retry` (`Status`, `NextRetryAt`, `CreatedAt`),
|
||||
KEY `idx_message_outbox_business` (`BusinessType`, `BusinessId`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
5
DatabaseScripts/stability_unique_constraints.sql
Normal file
5
DatabaseScripts/stability_unique_constraints.sql
Normal file
@ -0,0 +1,5 @@
|
||||
ALTER TABLE `CheckIn_Record`
|
||||
ADD UNIQUE KEY `uk_checkin_record_user_date_type_deleted` (`UserId`, `CheckInDate`, `Type`, `IsDeleted`);
|
||||
|
||||
ALTER TABLE `User_Bag`
|
||||
ADD UNIQUE KEY `uk_user_bag_available_item` (`UserId`, `ItemId`, `Status`, `IsDeleted`);
|
||||
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>
|
||||
|
||||
@ -33,6 +33,7 @@ public static class DependencyInjectionExtensions
|
||||
|
||||
services.AddTransient<GlobalExceptionMiddleware>();
|
||||
services.AddTransient<OperationLogMiddleware>();
|
||||
services.AddScoped<OperationLogActionFilter>();
|
||||
}
|
||||
|
||||
private static void AddJwtAuthentication(IServiceCollection services, IConfiguration configuration, IWebHostEnvironment? environment = null)
|
||||
@ -105,6 +106,7 @@ public static class DependencyInjectionExtensions
|
||||
var adminToken = await RedisHelper.GetAsync(JwtHelper.BuildAdminTokenKey(userId));
|
||||
if (adminToken == currentToken)
|
||||
{
|
||||
await RefreshRedisTokenExpiryAsync(JwtHelper.BuildAdminTokenKey(userId), currentToken, jwtSettings);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -115,6 +117,7 @@ public static class DependencyInjectionExtensions
|
||||
var wechatToken = await RedisHelper.GetAsync(JwtHelper.BuildWeChatTokenKey(wxUserId, userId));
|
||||
if (wechatToken == currentToken)
|
||||
{
|
||||
await RefreshRedisTokenExpiryAsync(JwtHelper.BuildWeChatTokenKey(wxUserId, userId), currentToken, jwtSettings);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -123,6 +126,14 @@ public static class DependencyInjectionExtensions
|
||||
};
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -1,99 +1,21 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using QYZH.InteractiveMagazine.Infrastructure.Auth;
|
||||
using QYZH.InteractiveMagazine.Models.Settings;
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
using System.Security.Claims;
|
||||
|
||||
namespace QYZH.InteractiveMagazine.Infrastructure.Middleware;
|
||||
|
||||
/// <summary>
|
||||
/// Refreshes the Redis session TTL for the current JWT when it is still the active token.
|
||||
/// 保留兼容的JWT自动刷新中间件,实际刷新在认证成功后执行。
|
||||
/// </summary>
|
||||
public class JwtAutoRefreshMiddleware
|
||||
{
|
||||
private readonly RequestDelegate _next;
|
||||
private readonly IConfiguration _configuration;
|
||||
|
||||
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(token);
|
||||
}
|
||||
|
||||
await _next(context);
|
||||
}
|
||||
|
||||
private async Task TryRefreshRedisTokenExpiryAsync(string token)
|
||||
{
|
||||
try
|
||||
{
|
||||
var tokenHandler = new JwtSecurityTokenHandler();
|
||||
if (tokenHandler.ReadToken(token) is not JwtSecurityToken jwtToken)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (jwtToken.ValidTo <= DateTime.UtcNow)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var userId = GetClaimValue(jwtToken, ClaimTypes.NameIdentifier, JwtRegisteredClaimNames.NameId);
|
||||
if (string.IsNullOrEmpty(userId))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var jwtSettings = _configuration.GetSection("JwtSettings").Get<JwtSettings>();
|
||||
if (jwtSettings == null || jwtSettings.ExpiryMinutes <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var wxUserId = GetClaimValue(jwtToken, JwtHelper.WxUserIdClaimType);
|
||||
await RefreshRedisTokenExpiryAsync(wxUserId, userId, token, jwtSettings.ExpiryMinutes);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore refresh failures. Authentication middleware will validate the request later.
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task RefreshRedisTokenExpiryAsync(string? wxUserId, string userId, string currentToken, int expiryMinutes)
|
||||
{
|
||||
var adminTokenKey = JwtHelper.BuildAdminTokenKey(userId);
|
||||
var adminToken = await RedisHelper.GetAsync(adminTokenKey);
|
||||
if (string.IsNullOrEmpty(wxUserId) && !string.IsNullOrEmpty(adminToken))
|
||||
{
|
||||
await RedisHelper.SetAsync(adminTokenKey, adminToken, TimeSpan.FromMinutes(expiryMinutes));
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(wxUserId))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var wechatTokenKey = JwtHelper.BuildWeChatTokenKey(wxUserId, userId);
|
||||
var wechatToken = await RedisHelper.GetAsync(wechatTokenKey);
|
||||
if (wechatToken == currentToken)
|
||||
{
|
||||
await RedisHelper.SetAsync(wechatTokenKey, currentToken, TimeSpan.FromMinutes(expiryMinutes));
|
||||
}
|
||||
}
|
||||
|
||||
private static string? GetClaimValue(JwtSecurityToken jwtToken, params string[] claimTypes)
|
||||
{
|
||||
return jwtToken.Claims.FirstOrDefault(c => claimTypes.Contains(c.Type))?.Value;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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 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;
|
||||
@ -11,14 +12,16 @@ namespace QYZH.InteractiveMagazine.Infrastructure.RabbitMQ
|
||||
{
|
||||
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, IConfiguration configuration)
|
||||
public RabbitMQService(IRabbitMQConnection connection, IConfiguration configuration, ILogger<RabbitMQService> logger)
|
||||
{
|
||||
_connection = connection ?? throw new ArgumentNullException(nameof(connection));
|
||||
_configuration = configuration;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
|
||||
@ -55,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;
|
||||
}
|
||||
}
|
||||
@ -115,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;
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
using QYZH.InteractiveMagazine.Models.Enum;
|
||||
|
||||
namespace QYZH.InteractiveMagazine.Models.Dto;
|
||||
|
||||
/// <summary>
|
||||
@ -148,6 +150,11 @@ public class AdminRoleInput
|
||||
/// </summary>
|
||||
public string Code { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 角色类型
|
||||
/// </summary>
|
||||
public AdminRoleTypeEnum Type { get; set; } = AdminRoleTypeEnum.Normal;
|
||||
|
||||
/// <summary>
|
||||
/// 备注
|
||||
/// </summary>
|
||||
@ -184,6 +191,11 @@ public class AdminRoleOutput
|
||||
/// </summary>
|
||||
public string Code { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 角色类型
|
||||
/// </summary>
|
||||
public AdminRoleTypeEnum Type { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 备注
|
||||
/// </summary>
|
||||
@ -224,6 +236,11 @@ public class AdminRoleSimpleOutput
|
||||
/// 角色编码
|
||||
/// </summary>
|
||||
public string Code { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 角色类型
|
||||
/// </summary>
|
||||
public AdminRoleTypeEnum Type { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -241,6 +258,11 @@ public class AdminRoleQueryInput : PageQueryModel
|
||||
/// </summary>
|
||||
public string? Code { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 角色类型
|
||||
/// </summary>
|
||||
public AdminRoleTypeEnum? Type { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 状态
|
||||
/// </summary>
|
||||
|
||||
@ -180,7 +180,7 @@ public class GenerateUserJournalQrCodeMessage
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 鏈熷垔浜岀淮鐮佹煡璇㈣緭鍏TO
|
||||
/// 期刊二维码查询输入DTO
|
||||
/// </summary>
|
||||
public class UserJournalQrCodeQueryInput : PageQueryModel
|
||||
{
|
||||
|
||||
@ -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;
|
||||
}
|
||||
@ -5,7 +5,7 @@ namespace QYZH.InteractiveMagazine.Models.Entity;
|
||||
/// <summary>
|
||||
/// 后台菜单表
|
||||
/// </summary>
|
||||
[SugarTable("AdminMenu")]
|
||||
[SugarTable("System_AdminMenu")]
|
||||
public partial class AdminMenu : SqlSugarBaseEntity
|
||||
{
|
||||
/// <summary>
|
||||
|
||||
@ -1,11 +1,12 @@
|
||||
using SqlSugar;
|
||||
using QYZH.InteractiveMagazine.Models.Enum;
|
||||
|
||||
namespace QYZH.InteractiveMagazine.Models.Entity;
|
||||
|
||||
/// <summary>
|
||||
/// 后台角色表
|
||||
/// </summary>
|
||||
[SugarTable("AdminRole")]
|
||||
[SugarTable("System_AdminRole")]
|
||||
public partial class AdminRole : SqlSugarBaseEntity
|
||||
{
|
||||
/// <summary>
|
||||
@ -22,6 +23,13 @@ public partial class AdminRole : SqlSugarBaseEntity
|
||||
/// </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:
|
||||
|
||||
@ -5,7 +5,7 @@ namespace QYZH.InteractiveMagazine.Models.Entity;
|
||||
/// <summary>
|
||||
/// 后台角色菜单关系表
|
||||
/// </summary>
|
||||
[SugarTable("AdminRoleMenu")]
|
||||
[SugarTable("System_AdminRoleMenu")]
|
||||
public partial class AdminRoleMenu : SqlSugarBaseEntity
|
||||
{
|
||||
/// <summary>
|
||||
|
||||
@ -6,7 +6,7 @@ namespace QYZH.InteractiveMagazine.Models.Entity
|
||||
///<summary>
|
||||
///后台管理员表
|
||||
///</summary>
|
||||
[SugarTable("AdminUser")]
|
||||
[SugarTable("System_AdminUser")]
|
||||
public partial class AdminUser : SqlSugarBaseEntity
|
||||
{
|
||||
public AdminUser(){
|
||||
|
||||
@ -5,7 +5,7 @@ namespace QYZH.InteractiveMagazine.Models.Entity;
|
||||
/// <summary>
|
||||
/// 后台管理员角色关系表
|
||||
/// </summary>
|
||||
[SugarTable("AdminUserRole")]
|
||||
[SugarTable("System_AdminUserRole")]
|
||||
public partial class AdminUserRole : SqlSugarBaseEntity
|
||||
{
|
||||
/// <summary>
|
||||
|
||||
@ -8,7 +8,7 @@ namespace QYZH.InteractiveMagazine.Models.Entity
|
||||
///<summary>
|
||||
///AIPrompt配置表
|
||||
///</summary>
|
||||
[SugarTable("AiBasePrompt")]
|
||||
[SugarTable("Ai_BasePrompt")]
|
||||
public partial class AiBasePrompt : SqlSugarBaseEntity
|
||||
{
|
||||
public AiBasePrompt(){
|
||||
|
||||
@ -6,7 +6,7 @@ namespace QYZH.InteractiveMagazine.Models.Entity;
|
||||
/// <summary>
|
||||
/// 轮播图表
|
||||
/// </summary>
|
||||
[SugarTable("Banner")]
|
||||
[SugarTable("Content_Banner")]
|
||||
public partial class Banner : SqlSugarBaseEntity
|
||||
{
|
||||
/// <summary>
|
||||
|
||||
@ -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(){
|
||||
|
||||
@ -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(){
|
||||
|
||||
@ -6,7 +6,7 @@ namespace QYZH.InteractiveMagazine.Models.Entity
|
||||
///<summary>
|
||||
///杂志任务;
|
||||
///</summary>
|
||||
[SugarTable("JournalPageTask")]
|
||||
[SugarTable("Journal_PageTask")]
|
||||
public class JournalPageTask : SqlSugarBaseEntity
|
||||
{
|
||||
/// <summary>
|
||||
|
||||
@ -8,7 +8,7 @@ namespace QYZH.InteractiveMagazine.Models.Entity
|
||||
///<summary>
|
||||
///杂志任务答案表;
|
||||
///</summary>
|
||||
[SugarTable("JournalPageTaskAnswer")]
|
||||
[SugarTable("Journal_PageTaskAnswer")]
|
||||
public partial class JournalPageTaskAnswer : SqlSugarBaseEntity
|
||||
{
|
||||
public JournalPageTaskAnswer(){
|
||||
|
||||
@ -6,7 +6,7 @@ namespace QYZH.InteractiveMagazine.Models.Entity
|
||||
/// <summary>
|
||||
/// 杂志任务用户作答记录
|
||||
/// </summary>
|
||||
[SugarTable("JournalPageTaskUserAnswer")]
|
||||
[SugarTable("Journal_PageTaskUserAnswer")]
|
||||
public class JournalPageTaskUserAnswer : SqlSugarBaseEntity
|
||||
{
|
||||
/// <summary>
|
||||
|
||||
@ -5,7 +5,7 @@ namespace QYZH.InteractiveMagazine.Models.Entity
|
||||
/// <summary>
|
||||
/// Snapshot record for a user answer.
|
||||
/// </summary>
|
||||
[SugarTable("JournalPageTaskUserAnswerSnapshot")]
|
||||
[SugarTable("Journal_PageTaskUserAnswerSnapshot")]
|
||||
public class JournalPageTaskUserAnswerSnapshot : SqlSugarBaseEntity
|
||||
{
|
||||
/// <summary>
|
||||
|
||||
@ -5,7 +5,7 @@ namespace QYZH.InteractiveMagazine.Models.Entity;
|
||||
/// <summary>
|
||||
/// 资料表
|
||||
/// </summary>
|
||||
[SugarTable("Material")]
|
||||
[SugarTable("Content_Material")]
|
||||
public partial class Material : SqlSugarBaseEntity
|
||||
{
|
||||
/// <summary>
|
||||
|
||||
@ -6,7 +6,7 @@ namespace QYZH.InteractiveMagazine.Models.Entity;
|
||||
/// <summary>
|
||||
/// 资料文件表
|
||||
/// </summary>
|
||||
[SugarTable("MaterialFile")]
|
||||
[SugarTable("Content_MaterialFile")]
|
||||
public partial class MaterialFile : SqlSugarBaseEntity
|
||||
{
|
||||
/// <summary>
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -5,7 +5,7 @@ namespace QYZH.InteractiveMagazine.Models.Entity
|
||||
/// <summary>
|
||||
/// 上传地址管理表
|
||||
/// </summary>
|
||||
[SugarTable("UploadDomain")]
|
||||
[SugarTable("System_UploadDomain")]
|
||||
public partial class UploadDomain : SqlSugarBaseEntity
|
||||
{
|
||||
/// <summary>
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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
|
||||
}
|
||||
@ -0,0 +1,33 @@
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace QYZH.InteractiveMagazine.Models.Enum;
|
||||
|
||||
/// <summary>
|
||||
/// 消息Outbox状态。
|
||||
/// </summary>
|
||||
public enum MessageOutboxStatusEnum
|
||||
{
|
||||
/// <summary>
|
||||
/// 待发送。
|
||||
/// </summary>
|
||||
[Description("待发送")]
|
||||
Pending = 0,
|
||||
|
||||
/// <summary>
|
||||
/// 已发送。
|
||||
/// </summary>
|
||||
[Description("已发送")]
|
||||
Sent = 1,
|
||||
|
||||
/// <summary>
|
||||
/// 发送失败待重试。
|
||||
/// </summary>
|
||||
[Description("发送失败待重试")]
|
||||
Failed = 2,
|
||||
|
||||
/// <summary>
|
||||
/// 已放弃。
|
||||
/// </summary>
|
||||
[Description("已放弃")]
|
||||
Abandoned = 3
|
||||
}
|
||||
@ -0,0 +1,12 @@
|
||||
namespace QYZH.InteractiveMagazine.Models.Settings;
|
||||
|
||||
/// <summary>
|
||||
/// Hangfire存储配置。
|
||||
/// </summary>
|
||||
public class HangfireStorageSettings
|
||||
{
|
||||
/// <summary>
|
||||
/// 存储类型,当前默认 Memory,可配置为 Redis。
|
||||
/// </summary>
|
||||
public string StorageType { get; set; } = "Memory";
|
||||
}
|
||||
@ -0,0 +1,17 @@
|
||||
namespace QYZH.InteractiveMagazine.Models.Settings;
|
||||
|
||||
/// <summary>
|
||||
/// RabbitMQ重试配置。
|
||||
/// </summary>
|
||||
public class RabbitMQRetrySettings
|
||||
{
|
||||
/// <summary>
|
||||
/// 最大重试次数。
|
||||
/// </summary>
|
||||
public int MaxRetryCount { get; set; } = 3;
|
||||
|
||||
/// <summary>
|
||||
/// 重试延迟毫秒数。
|
||||
/// </summary>
|
||||
public int RetryDelayMilliseconds { get; set; } = 30000;
|
||||
}
|
||||
@ -0,0 +1,12 @@
|
||||
namespace QYZH.InteractiveMagazine.Models.Settings;
|
||||
|
||||
/// <summary>
|
||||
/// 雪花ID配置。
|
||||
/// </summary>
|
||||
public class SnowflakeSettings
|
||||
{
|
||||
/// <summary>
|
||||
/// WorkerId,生产部署时每个写库进程必须唯一。
|
||||
/// </summary>
|
||||
public ushort WorkerId { get; set; }
|
||||
}
|
||||
@ -47,10 +47,13 @@ public class AutoDotCodeConsumer(
|
||||
|
||||
using var scope = scopeFactory.CreateScope();
|
||||
var dbContext = scope.ServiceProvider.GetRequiredService<ISqlSugarClient>();
|
||||
JournalPagePrintDto? request = null;
|
||||
List<long> reservedDotDetailIds = [];
|
||||
var dotPagesCommitted = false;
|
||||
|
||||
try
|
||||
{
|
||||
var request = JsonSerializer.Deserialize<JournalPagePrintDto>(message, new JsonSerializerOptions
|
||||
request = JsonSerializer.Deserialize<JournalPagePrintDto>(message, new JsonSerializerOptions
|
||||
{
|
||||
PropertyNameCaseInsensitive = true
|
||||
});
|
||||
@ -72,6 +75,7 @@ public class AutoDotCodeConsumer(
|
||||
if (string.IsNullOrWhiteSpace(journalPdfKey))
|
||||
{
|
||||
logger.LogError("书籍上传 PDF 文件地址为空,JournalId: {JournalId}", request.JournalId);
|
||||
await TryCallbackCodeFailAsync(request, cancellationToken);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -79,6 +83,7 @@ public class AutoDotCodeConsumer(
|
||||
if (pdfStream == null)
|
||||
{
|
||||
logger.LogError("获取书籍上传 PDF 文件失败,OSS Key: {OssKey}", journalPdfKey);
|
||||
await TryCallbackCodeFailAsync(request, cancellationToken);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -93,6 +98,7 @@ public class AutoDotCodeConsumer(
|
||||
if (dotFile == null)
|
||||
{
|
||||
logger.LogError("配置打印数据错误,点阵文件不存在,DotId: {DotId}", dotId);
|
||||
await TryCallbackCodeFailAsync(request, cancellationToken);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -101,7 +107,7 @@ public class AutoDotCodeConsumer(
|
||||
if (!File.Exists(exePath))
|
||||
{
|
||||
logger.LogError("PrintTool.exe 不存在,路径:{ExePath}", exePath);
|
||||
await ExecuteUpdateJournalStatusAsync(BuildFailResponse(request), cancellationToken);
|
||||
await TryCallbackCodeFailAsync(request, cancellationToken);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -109,7 +115,7 @@ public class AutoDotCodeConsumer(
|
||||
if (!File.Exists(xmlPath))
|
||||
{
|
||||
logger.LogError("铺码授权文件不存在,路径:{XmlPath}", xmlPath);
|
||||
await ExecuteUpdateJournalStatusAsync(BuildFailResponse(request), cancellationToken);
|
||||
await TryCallbackCodeFailAsync(request, cancellationToken);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -126,11 +132,26 @@ public class AutoDotCodeConsumer(
|
||||
if (dotFileDetailList.Count < pageNumMax)
|
||||
{
|
||||
logger.LogError("点阵页码余量不足,DotId: {DotId}, Need: {Need}, Available: {Available}", dotId, pageNumMax, dotFileDetailList.Count);
|
||||
await ExecuteUpdateJournalStatusAsync(BuildFailResponse(request), cancellationToken);
|
||||
await TryCallbackCodeFailAsync(request, cancellationToken);
|
||||
return;
|
||||
}
|
||||
|
||||
var dotFileDetailPageName = dotFileDetailList.Select(x => x.PageName).OrderBy(x => x).ToArray();
|
||||
reservedDotDetailIds = dotFileDetailList.Select(x => x.Id).ToList();
|
||||
var reservedRows = await dbContext.Updateable<DotFileDetail>()
|
||||
.SetColumns(x => x.IsUse == true)
|
||||
.SetColumns(x => x.UpdatedAt == DateTime.Now)
|
||||
.Where(x => reservedDotDetailIds.Contains(x.Id) && !x.IsUse)
|
||||
.ExecuteCommandAsync(cancellationToken);
|
||||
if (reservedRows != reservedDotDetailIds.Count)
|
||||
{
|
||||
logger.LogError("点阵页码预占失败,DotId: {DotId}, Need: {Need}, Reserved: {Reserved}", dotId, reservedDotDetailIds.Count, reservedRows);
|
||||
await ReleaseReservedDotPagesAsync(dbContext, reservedDotDetailIds, cancellationToken);
|
||||
reservedDotDetailIds.Clear();
|
||||
await TryCallbackCodeFailAsync(request, cancellationToken, dotFileDetailPageName);
|
||||
return;
|
||||
}
|
||||
|
||||
var pageStr = "{" + $"[{dotFileDetailList[0].PageName},{pageNumMax}]" + "}";
|
||||
var arguments = $"-sMode=Generate -sPDF=\"{uploadFilePath}\" -sLIC=\"{xmlPath}\" -pStart=1 -oPDF=\"{downloadFilePath}\" -dPageAddr=1 -dPrint={dPrint} -dDotSize=40 -dType=0 -dOutFile=0 -dControlPageNum={pageStr}";
|
||||
var printResult = await ExecutePrintToolAsync(exePath, printToolDirectory, arguments, cancellationToken);
|
||||
@ -138,7 +159,9 @@ public class AutoDotCodeConsumer(
|
||||
if (printResult.Timeout || printResult.ExitCode != 0)
|
||||
{
|
||||
logger.LogError("执行 PrintTool.exe 失败,退出码:{ExitCode},错误信息:{ErrorMessage}", printResult.ExitCode, printResult.Error);
|
||||
await ExecuteUpdateJournalStatusAsync(BuildFailResponse(request, dotFileDetailPageName), cancellationToken);
|
||||
await ReleaseReservedDotPagesAsync(dbContext, reservedDotDetailIds, cancellationToken);
|
||||
reservedDotDetailIds.Clear();
|
||||
await TryCallbackCodeFailAsync(request, cancellationToken, dotFileDetailPageName);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -150,8 +173,9 @@ public class AutoDotCodeConsumer(
|
||||
|
||||
if (!ValidatePrintOutput(printResult.Output, dotFileDetailPageName, dPrint))
|
||||
{
|
||||
statusModel.Status = JournalStatusEnum.CodeFail;
|
||||
await ExecuteUpdateJournalStatusAsync(statusModel, cancellationToken);
|
||||
await ReleaseReservedDotPagesAsync(dbContext, reservedDotDetailIds, cancellationToken);
|
||||
reservedDotDetailIds.Clear();
|
||||
await TryCallbackCodeFailAsync(request, cancellationToken, dotFileDetailPageName);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -186,16 +210,32 @@ public class AutoDotCodeConsumer(
|
||||
.Where(x => x.Id == dotId)
|
||||
.ExecuteCommandAsync();
|
||||
});
|
||||
dotPagesCommitted = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
await ReleaseReservedDotPagesAsync(dbContext, reservedDotDetailIds, cancellationToken);
|
||||
reservedDotDetailIds.Clear();
|
||||
logger.LogError("回调接口修改书籍状态失败,JournalId: {JournalId},接口返回消息:{Message}", statusModel.JournalId, callbackResponse.Message);
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
logger.LogWarning("自动铺码处理已取消,JournalId: {JournalId}", request?.JournalId);
|
||||
throw;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "自动铺码处理失败");
|
||||
throw;
|
||||
if (request != null)
|
||||
{
|
||||
if (!dotPagesCommitted && reservedDotDetailIds.Count > 0)
|
||||
{
|
||||
await ReleaseReservedDotPagesAsync(dbContext, reservedDotDetailIds, CancellationToken.None);
|
||||
}
|
||||
|
||||
await TryCallbackCodeFailAsync(request, CancellationToken.None);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -298,6 +338,40 @@ public class AutoDotCodeConsumer(
|
||||
return new CallbackUpdateJournalStatusResponse();
|
||||
}
|
||||
|
||||
private async Task TryCallbackCodeFailAsync(JournalPagePrintDto request, CancellationToken cancellationToken, string[]? pageNo = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var callbackResponse = await ExecuteUpdateJournalStatusAsync(BuildFailResponse(request, pageNo), cancellationToken);
|
||||
if (!callbackResponse.IsSuccess)
|
||||
{
|
||||
logger.LogError("回调接口修改书籍为铺码失败状态失败,JournalId: {JournalId},接口返回消息:{Message}", request.JournalId, callbackResponse.Message);
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (Exception callbackEx)
|
||||
{
|
||||
logger.LogError(callbackEx, "铺码失败后请求回调接口失败,JournalId: {JournalId}", request.JournalId);
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task ReleaseReservedDotPagesAsync(ISqlSugarClient dbContext, List<long> dotDetailIds, CancellationToken cancellationToken)
|
||||
{
|
||||
if (dotDetailIds.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await dbContext.Updateable<DotFileDetail>()
|
||||
.SetColumns(x => x.IsUse == false)
|
||||
.SetColumns(x => x.UpdatedAt == DateTime.Now)
|
||||
.Where(x => dotDetailIds.Contains(x.Id))
|
||||
.ExecuteCommandAsync(cancellationToken);
|
||||
}
|
||||
|
||||
private static JournalPagePrintDto BuildFailResponse(JournalPagePrintDto request, string[]? pageNo = null)
|
||||
{
|
||||
return new JournalPagePrintDto
|
||||
|
||||
@ -55,7 +55,7 @@ public class RabbitMQHostedService(IServiceProvider serviceProvider, ILogger<Rab
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "消费者 {QueueName} 处理消息异常", queueName);
|
||||
var dlqSent = await SendToDeadLetterQueueAsync(exchange, dlqName, dlqRoutingKey, ea.Body.ToArray(), stoppingToken);
|
||||
var dlqSent = await SendToDeadLetterQueueAsync(exchange, dlqName, dlqRoutingKey, queueName, ea.Body.ToArray(), ex, stoppingToken);
|
||||
await channel.BasicNackAsync(ea.DeliveryTag, false, !dlqSent, stoppingToken);
|
||||
|
||||
try
|
||||
@ -79,7 +79,7 @@ public class RabbitMQHostedService(IServiceProvider serviceProvider, ILogger<Rab
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<bool> SendToDeadLetterQueueAsync(string exchange, string dlqName, string dlqRoutingKey, byte[] body, CancellationToken cancellationToken)
|
||||
private async Task<bool> SendToDeadLetterQueueAsync(string exchange, string dlqName, string dlqRoutingKey, string originalQueueName, byte[] body, Exception exception, CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
@ -90,7 +90,14 @@ public class RabbitMQHostedService(IServiceProvider serviceProvider, ILogger<Rab
|
||||
|
||||
var properties = new RabbitMQ.Client.BasicProperties
|
||||
{
|
||||
Persistent = true
|
||||
Persistent = true,
|
||||
Headers = new Dictionary<string, object?>
|
||||
{
|
||||
["x-original-queue"] = originalQueueName,
|
||||
["x-error-type"] = exception.GetType().FullName,
|
||||
["x-error-message"] = exception.Message,
|
||||
["x-failed-at"] = DateTimeOffset.UtcNow.ToString("O")
|
||||
}
|
||||
};
|
||||
await channel.BasicPublishAsync(exchange, dlqRoutingKey, false, properties, body, cancellationToken);
|
||||
logger.LogInformation("消息已发送到死信队列: {DlqName}", dlqName);
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
using QYZH.InteractiveMagazine.Infrastructure.RabbitMQ;
|
||||
using QYZH.InteractiveMagazine.Infrastructure.Redis;
|
||||
using QYZH.InteractiveMagazine.Infrastructure.SDK;
|
||||
using QYZH.InteractiveMagazine.Models.Settings;
|
||||
using QYZH.InteractiveMagazine.PrintWorker.Consumers;
|
||||
using Serilog;
|
||||
using SqlSugar;
|
||||
@ -34,7 +35,8 @@ Log.Logger = new LoggerConfiguration()
|
||||
builder.Services.AddSerilog();
|
||||
builder.Services.AddWindowsService(options => options.ServiceName = serviceName);
|
||||
|
||||
YitIdHelper.SetIdGenerator(new IdGeneratorOptions { WorkerId = 3 });
|
||||
var snowflakeSettings = builder.Configuration.GetSection("SnowflakeSettings").Get<SnowflakeSettings>() ?? new SnowflakeSettings { WorkerId = 3 };
|
||||
YitIdHelper.SetIdGenerator(new IdGeneratorOptions { WorkerId = snowflakeSettings.WorkerId });
|
||||
|
||||
builder.Services.AddSqlSugar(new IocConfig
|
||||
{
|
||||
|
||||
@ -2,6 +2,9 @@
|
||||
"ConnectionStrings": {
|
||||
"DefaultConnection": "server=192.168.20.150;port=13306;database=InteractiveMagazine;user=user;password=n68792bu!y99r905;charset=utf8mb4;"
|
||||
},
|
||||
"SnowflakeSettings": {
|
||||
"WorkerId": 3
|
||||
},
|
||||
"RedisSettings": {
|
||||
"ConnectionString": "192.168.20.150:16379,defaultDatabase=5",
|
||||
"Sentinels": [],
|
||||
@ -14,6 +17,10 @@
|
||||
"Password": "@ss%&*otz%d*pq2S",
|
||||
"VirtualHost": "InteractiveMagazine"
|
||||
},
|
||||
"RabbitMQRetrySettings": {
|
||||
"MaxRetryCount": 3,
|
||||
"RetryDelayMilliseconds": 30000
|
||||
},
|
||||
"Serilog": {
|
||||
"MinimumLevel": {
|
||||
"Default": "Information",
|
||||
|
||||
@ -253,9 +253,9 @@ namespace QYZH.InteractiveMagazine.Repository
|
||||
var total = 0;
|
||||
page.PageSize = parm.PageSize;
|
||||
page.PageIndex = parm.PageIndex;
|
||||
if (string.IsNullOrEmpty(parm.Sort))
|
||||
if (!string.IsNullOrWhiteSpace(parm.Sort))
|
||||
{
|
||||
source.OrderByPropertyName(parm.Sort, parm.SortType.Contains("desc") ? OrderByType.Desc : OrderByType.Asc);
|
||||
source.OrderByPropertyName(parm.Sort, parm.SortType?.Contains("desc", StringComparison.OrdinalIgnoreCase) == true ? OrderByType.Desc : OrderByType.Asc);
|
||||
}
|
||||
page.Result = source
|
||||
//.OrderByIF(parm.Sort.IsNotEmpty(), $"{parm.Sort.ToSqlFilter()} {(!string.IsNullOrWhiteSpace(parm.SortType) && parm.SortType.Contains("desc") ? "desc" : "asc")}")
|
||||
@ -278,9 +278,9 @@ namespace QYZH.InteractiveMagazine.Repository
|
||||
var total = 0;
|
||||
page.PageSize = parm.PageSize;
|
||||
page.PageIndex = parm.PageIndex;
|
||||
if (string.IsNullOrEmpty(parm.Sort))
|
||||
if (!string.IsNullOrWhiteSpace(parm.Sort))
|
||||
{
|
||||
source.OrderByPropertyName(parm.Sort, parm.SortType.Contains("desc") ? OrderByType.Desc : OrderByType.Asc);
|
||||
source.OrderByPropertyName(parm.Sort, parm.SortType?.Contains("desc", StringComparison.OrdinalIgnoreCase) == true ? OrderByType.Desc : OrderByType.Asc);
|
||||
}
|
||||
var result = source
|
||||
//.OrderByIF(parm.Sort.IsNotEmpty(), $"{parm.Sort.ToSqlFilter()} {(!string.IsNullOrWhiteSpace(parm.SortType) && parm.SortType.Contains("desc") ? "desc" : "asc")}")
|
||||
|
||||
@ -128,6 +128,7 @@ public class AdminPermissionService(
|
||||
{
|
||||
Name = input.Name.Trim(),
|
||||
Code = input.Code.Trim(),
|
||||
Type = input.Type,
|
||||
Remark = input.Remark?.Trim(),
|
||||
Status = input.Status,
|
||||
CreatedBy = "System",
|
||||
@ -157,9 +158,11 @@ public class AdminPermissionService(
|
||||
BusinessException.ThrowIf(role == null, "角色不存在", ResultCode.NOT_FOUND);
|
||||
|
||||
await ValidateRoleInputAsync(input, id);
|
||||
BusinessException.ThrowIf(role!.Type == AdminRoleTypeEnum.System && input.Type != AdminRoleTypeEnum.System, "系统角色不能修改为普通角色", ResultCode.FORBIDDEN);
|
||||
|
||||
role!.Name = input.Name.Trim();
|
||||
role.Name = input.Name.Trim();
|
||||
role.Code = input.Code.Trim();
|
||||
role.Type = input.Type;
|
||||
role.Remark = input.Remark?.Trim();
|
||||
role.Status = input.Status;
|
||||
role.UpdatedBy = "System";
|
||||
@ -182,6 +185,7 @@ public class AdminPermissionService(
|
||||
public async Task DeleteRoleAsync(long id)
|
||||
{
|
||||
var role = await adminRoleRepository.GetByIdAsync(id);
|
||||
BusinessException.ThrowIf(role?.Type == AdminRoleTypeEnum.System, "系统角色不允许删除", ResultCode.FORBIDDEN);
|
||||
BusinessException.ThrowIf(role == null, "角色不存在", ResultCode.NOT_FOUND);
|
||||
|
||||
var usedByUser = await adminUserRoleRepository.Queryable().AnyAsync(x => x.RoleId == id && !x.IsDeleted);
|
||||
@ -221,6 +225,7 @@ public class AdminPermissionService(
|
||||
.Where(x => !x.IsDeleted)
|
||||
.WhereIF(!string.IsNullOrWhiteSpace(input.Name), x => x.Name.Contains(input.Name!))
|
||||
.WhereIF(!string.IsNullOrWhiteSpace(input.Code), x => x.Code.Contains(input.Code!))
|
||||
.WhereIF(input.Type.HasValue, x => x.Type == input.Type!.Value)
|
||||
.WhereIF(input.Status.HasValue, x => x.Status == input.Status!.Value)
|
||||
.OrderByDescending(x => x.CreatedAt)
|
||||
.ToPageListAsync(input.PageIndex, input.PageSize, totalNumber);
|
||||
@ -283,7 +288,8 @@ public class AdminPermissionService(
|
||||
{
|
||||
Id = role.Id,
|
||||
Name = role.Name,
|
||||
Code = role.Code
|
||||
Code = role.Code,
|
||||
Type = role.Type
|
||||
})
|
||||
.ToListAsync();
|
||||
}
|
||||
@ -490,6 +496,7 @@ public class AdminPermissionService(
|
||||
Id = role.Id,
|
||||
Name = role.Name,
|
||||
Code = role.Code,
|
||||
Type = role.Type,
|
||||
Remark = role.Remark,
|
||||
Status = role.Status,
|
||||
MenuIds = menuIds,
|
||||
|
||||
@ -74,6 +74,14 @@ public class CheckInService(
|
||||
|
||||
await checkInRecordRepository.UseTranAsync(async () =>
|
||||
{
|
||||
var duplicate = await checkInRecordRepository.Context.Queryable<CheckInRecord>()
|
||||
.Where(r => r.UserId == userId && !r.IsDeleted && r.CheckInDate >= today && r.CheckInDate < today.AddDays(1))
|
||||
.AnyAsync();
|
||||
if (duplicate)
|
||||
{
|
||||
throw new BusinessException("今日已签到,请明天再来", ResultCode.CONFLICT);
|
||||
}
|
||||
|
||||
// 6a. 创建签到记录
|
||||
var checkInRecord = new CheckInRecord
|
||||
{
|
||||
@ -94,10 +102,16 @@ public class CheckInService(
|
||||
var newGrowthBalance = user.GrowthPoints + growthReward;
|
||||
|
||||
await checkInRecordRepository.Context.Updateable<Users>()
|
||||
.SetColumns(u => u.GrowthPoints == newGrowthBalance)
|
||||
.SetColumns(u => u.GrowthPoints == u.GrowthPoints + growthReward)
|
||||
.SetColumns(u => u.UpdatedAt == DateTime.Now)
|
||||
.Where(u => u.Id == userId && !u.IsDeleted)
|
||||
.ExecuteCommandAsync();
|
||||
|
||||
newGrowthBalance = await checkInRecordRepository.Context.Queryable<Users>()
|
||||
.Where(u => u.Id == userId && !u.IsDeleted)
|
||||
.Select(u => u.GrowthPoints)
|
||||
.FirstAsync();
|
||||
|
||||
// 6c. 通过积分服务增加积分
|
||||
var pointsResult = await pointsService.AddPointsInTranAsync(new AddPointsInput
|
||||
{
|
||||
@ -271,6 +285,14 @@ public class CheckInService(
|
||||
|
||||
await checkInRecordRepository.UseTranAsync(async () =>
|
||||
{
|
||||
var duplicate = await checkInRecordRepository.Context.Queryable<CheckInRecord>()
|
||||
.Where(r => r.UserId == userId && !r.IsDeleted && r.CheckInDate >= targetDate && r.CheckInDate < targetDate.AddDays(1))
|
||||
.AnyAsync();
|
||||
if (duplicate)
|
||||
{
|
||||
throw new BusinessException($"{targetDate:yyyy-MM-dd} 已签到,无需补签", ResultCode.CONFLICT);
|
||||
}
|
||||
|
||||
// 创建补签记录
|
||||
var checkInRecord = new CheckInRecord
|
||||
{
|
||||
@ -290,14 +312,17 @@ public class CheckInService(
|
||||
var recordId = await checkInRecordRepository.Insertable(checkInRecord).ExecuteReturnIdentityAsync();
|
||||
checkInRecord.Id = recordId;
|
||||
|
||||
// 更新用户成长值
|
||||
var newGrowthBalance = user.GrowthPoints + growthReward;
|
||||
|
||||
await checkInRecordRepository.Context.Updateable<Users>()
|
||||
.SetColumns(u => u.GrowthPoints == newGrowthBalance)
|
||||
.SetColumns(u => u.GrowthPoints == u.GrowthPoints + growthReward)
|
||||
.SetColumns(u => u.UpdatedAt == DateTime.Now)
|
||||
.Where(u => u.Id == userId && !u.IsDeleted)
|
||||
.ExecuteCommandAsync();
|
||||
|
||||
var newGrowthBalance = await checkInRecordRepository.Context.Queryable<Users>()
|
||||
.Where(u => u.Id == userId && !u.IsDeleted)
|
||||
.Select(u => u.GrowthPoints)
|
||||
.FirstAsync();
|
||||
|
||||
// 通过积分服务增加积分
|
||||
var pointsResult = await pointsService.AddPointsInTranAsync(new AddPointsInput
|
||||
{
|
||||
@ -311,19 +336,20 @@ public class CheckInService(
|
||||
// 扣减补签卡
|
||||
if (makeUpCard.Quantity <= 1)
|
||||
{
|
||||
var cardAffectedRows = await checkInRecordRepository.Context.Updateable<UserBag>()
|
||||
.SetColumns(b => b.Quantity == b.Quantity - 1)
|
||||
.SetColumns(b => b.UpdatedAt == DateTime.Now)
|
||||
.Where(b => b.Id == makeUpCard.Id && b.UserId == userId && b.Quantity > 0 && b.Status == (int)UserBagStatusEnum.Available && !b.IsDeleted)
|
||||
.ExecuteCommandAsync();
|
||||
if (cardAffectedRows <= 0)
|
||||
{
|
||||
throw new BusinessException("补签卡不足,无法补签", ResultCode.CONFLICT);
|
||||
}
|
||||
|
||||
await checkInRecordRepository.Context.Updateable<UserBag>()
|
||||
.SetColumns(b => b.Status == (int)UserBagStatusEnum.Expired)
|
||||
.SetColumns(b => b.Quantity == 0)
|
||||
.SetColumns(b => b.UpdatedAt == DateTime.Now)
|
||||
.Where(b => b.Id == makeUpCard.Id)
|
||||
.ExecuteCommandAsync();
|
||||
}
|
||||
else
|
||||
{
|
||||
await checkInRecordRepository.Context.Updateable<UserBag>()
|
||||
.SetColumns(b => b.Quantity == makeUpCard.Quantity - 1)
|
||||
.SetColumns(b => b.UpdatedAt == DateTime.Now)
|
||||
.Where(b => b.Id == makeUpCard.Id)
|
||||
.Where(b => b.Id == makeUpCard.Id && b.Quantity <= 0 && !b.IsDeleted)
|
||||
.ExecuteCommandAsync();
|
||||
}
|
||||
|
||||
|
||||
@ -98,11 +98,16 @@ public class CompensationManageService(
|
||||
UserId = task.UserId
|
||||
});
|
||||
|
||||
await operationLogService.LogAsync(
|
||||
operatorId, operatorName,
|
||||
OperationLogActionType.CompensationRetry,
|
||||
OperationLogTargetType.CompensationTask,
|
||||
taskId, null, detail, ipAddress);
|
||||
await operationLogService.LogAsync(new OperationLogRecordInput
|
||||
{
|
||||
OperatorId = operatorId,
|
||||
OperatorName = operatorName,
|
||||
ActionType = OperationLogActionType.CompensationRetry,
|
||||
TargetType = OperationLogTargetType.CompensationTask,
|
||||
TargetId = taskId,
|
||||
Detail = detail,
|
||||
IpAddress = ipAddress
|
||||
});
|
||||
|
||||
logger.LogInformation("补偿任务手动重试成功,TaskId: {TaskId}", taskId);
|
||||
}
|
||||
@ -140,11 +145,16 @@ public class CompensationManageService(
|
||||
UserId = task.UserId
|
||||
});
|
||||
|
||||
await operationLogService.LogAsync(
|
||||
operatorId, operatorName,
|
||||
OperationLogActionType.CompensationResolve,
|
||||
OperationLogTargetType.CompensationTask,
|
||||
taskId, null, detail, ipAddress);
|
||||
await operationLogService.LogAsync(new OperationLogRecordInput
|
||||
{
|
||||
OperatorId = operatorId,
|
||||
OperatorName = operatorName,
|
||||
ActionType = OperationLogActionType.CompensationResolve,
|
||||
TargetType = OperationLogTargetType.CompensationTask,
|
||||
TargetId = taskId,
|
||||
Detail = detail,
|
||||
IpAddress = ipAddress
|
||||
});
|
||||
|
||||
logger.LogInformation("补偿任务标记已解决成功,TaskId: {TaskId}", taskId);
|
||||
}
|
||||
|
||||
@ -11,6 +11,7 @@ using QYZH.InteractiveMagazine.IService;
|
||||
using QYZH.InteractiveMagazine.Models.Common;
|
||||
using QYZH.InteractiveMagazine.Models.Dto;
|
||||
using QYZH.InteractiveMagazine.Models.Dto.Journal;
|
||||
using QYZH.InteractiveMagazine.Models.Dto.RabbitMQ;
|
||||
using QYZH.InteractiveMagazine.Models.Entity;
|
||||
using QYZH.InteractiveMagazine.Models.Enum;
|
||||
using QYZH.InteractiveMagazine.Repository;
|
||||
@ -24,7 +25,7 @@ public class JournalPageService(BaseRepository<Journal> JournalRepository,
|
||||
OssService ossService,
|
||||
IHttpClientFactory httpClientFactory,
|
||||
IConfiguration configuration,
|
||||
IRabbitMQService rabbitMqService,
|
||||
IMessagePublishService messagePublishService,
|
||||
ILogger<AiBasePromptService> logger,
|
||||
BaseRepository<JournalPage> JournalPageRepository,
|
||||
BaseRepository<JournalPageTask> JournalPageTaskRepository,
|
||||
@ -135,18 +136,29 @@ public class JournalPageService(BaseRepository<Journal> JournalRepository,
|
||||
var isPdfExists = ossService.DoesObjectExist(uploadPdfKey);
|
||||
BusinessException.ThrowIf(!isPdfExists, "获取期刊上传的PDF文件失败,请检查PDF文件是否上传成功", ResultCode.GLOBAL_ERROR);
|
||||
|
||||
journalEntity.Status = (int)JournalStatusEnum.Codeing;
|
||||
|
||||
await JournalRepository.Updateable(journalEntity).UpdateColumns(x => new { x.Status, x.UpdatedAt }).ExecuteCommandAsync();
|
||||
|
||||
var data = new JournalPagePrintDto
|
||||
{
|
||||
JournalId = JournalId,
|
||||
JournalPdfUrl = uploadPdfUrl,
|
||||
PageNum = [.. journalPageList.Select(x => x.PageNum)]
|
||||
};
|
||||
var msRes = await rabbitMqService.SendAsync(new RabbitMQSendParam { Exchange = "ex.journal", Queue = "mq.journal.dotcode.auto", RoutingKey = "rk.journal.dotcode.auto", Data = data });//发生消息
|
||||
return msRes;
|
||||
|
||||
return await UseTranAsync(async () =>
|
||||
{
|
||||
journalEntity.Status = (int)JournalStatusEnum.Codeing;
|
||||
journalEntity.UpdatedAt = DateTime.Now;
|
||||
await JournalRepository.Updateable(journalEntity).UpdateColumns(x => new { x.Status, x.UpdatedAt }).ExecuteCommandAsync();
|
||||
await messagePublishService.PublishAsync(new MessagePublishInput<JournalPagePrintDto>
|
||||
{
|
||||
Exchange = "ex.journal",
|
||||
Queue = "mq.journal.dotcode.auto",
|
||||
RoutingKey = "rk.journal.dotcode.auto",
|
||||
Data = data,
|
||||
BusinessType = "JournalDotCode",
|
||||
BusinessId = JournalId
|
||||
});
|
||||
return true;
|
||||
});
|
||||
}
|
||||
/// <summary>
|
||||
/// 回调接口-自动铺码, 书页铺码后回调接口,更新书页的点阵码
|
||||
|
||||
@ -9,10 +9,12 @@ using QYZH.InteractiveMagazine.Models.Common;
|
||||
using QYZH.InteractiveMagazine.Models.Dto;
|
||||
using QYZH.InteractiveMagazine.Models.Dto.DotMatrix;
|
||||
using QYZH.InteractiveMagazine.Models.Dto.Journal;
|
||||
using QYZH.InteractiveMagazine.Models.Dto.RabbitMQ;
|
||||
using QYZH.InteractiveMagazine.Models.Entity;
|
||||
using QYZH.InteractiveMagazine.Models.Enum;
|
||||
using QYZH.InteractiveMagazine.Repository;
|
||||
using SqlSugar;
|
||||
using System.Text.Json;
|
||||
using Yitter.IdGenerator;
|
||||
|
||||
namespace QYZH.InteractiveMagazine.Service;
|
||||
@ -24,7 +26,7 @@ public class JournalService(BaseRepository<JournalPage> JournalPageRepository,
|
||||
BaseRepository<DotFile> dotFileRepository,
|
||||
BaseRepository<DotFileDetail> dotFileDetailRepository,
|
||||
OssService ossService,
|
||||
IRabbitMQService rabbitMqService,
|
||||
IMessagePublishService messagePublishService,
|
||||
ILogger<JournalService> logger) : BaseRepository<Journal>, IJournalService
|
||||
{
|
||||
private const string JournalExchange = "ex.journal";
|
||||
@ -375,23 +377,27 @@ public class JournalService(BaseRepository<JournalPage> JournalPageRepository,
|
||||
x => x.Key,
|
||||
x => string.Join(',', x.OrderBy(q => ParseTaskNo(q.No)).Select(q => q.Id)));
|
||||
|
||||
var bookMessageSent = await rabbitMqService.SendAsync(new RabbitMQSendParam
|
||||
var publishMessages = new List<MessagePublishInput<object>>
|
||||
{
|
||||
Exchange = JournalExchange,
|
||||
Queue = PublishBookQueue,
|
||||
RoutingKey = PublishBookRoutingKey,
|
||||
Data = new JournalPublishBookMessage
|
||||
new()
|
||||
{
|
||||
BookId = id,
|
||||
StartTime = book.StartTime.Value,
|
||||
EndTime = book.EndTime.Value
|
||||
Exchange = JournalExchange,
|
||||
Queue = PublishBookQueue,
|
||||
RoutingKey = PublishBookRoutingKey,
|
||||
Data = new JournalPublishBookMessage
|
||||
{
|
||||
BookId = id,
|
||||
StartTime = book.StartTime.Value,
|
||||
EndTime = book.EndTime.Value
|
||||
},
|
||||
BusinessType = "JournalPublish",
|
||||
BusinessId = id
|
||||
}
|
||||
});
|
||||
BusinessException.ThrowIf(!bookMessageSent, "发布书籍消息发送失败", ResultCode.GLOBAL_ERROR);
|
||||
};
|
||||
|
||||
foreach (var page in pages)
|
||||
{
|
||||
var pageMessageSent = await rabbitMqService.SendAsync(new RabbitMQSendParam
|
||||
publishMessages.Add(new MessagePublishInput<object>
|
||||
{
|
||||
Exchange = JournalExchange,
|
||||
Queue = PublishBookPageQueue,
|
||||
@ -404,16 +410,21 @@ public class JournalService(BaseRepository<JournalPage> JournalPageRepository,
|
||||
Layout = page.Layout,
|
||||
Url = DomainHelper.OssFullUrl(page.Url),
|
||||
QuestionNo = questionIdsByPageId.GetValueOrDefault(page.Id) ?? string.Empty
|
||||
}
|
||||
},
|
||||
BusinessType = "JournalPublish",
|
||||
BusinessId = id
|
||||
});
|
||||
BusinessException.ThrowIf(!pageMessageSent, $"发布书页消息发送失败,PageId: {page.Id}", ResultCode.GLOBAL_ERROR);
|
||||
}
|
||||
|
||||
return await base.Updateable()
|
||||
.SetColumns(s => s.Status, JournalStatusEnum.Published)
|
||||
.SetColumns(s => s.UpdatedAt, DateTime.Now)
|
||||
.Where(w => w.Id == id)
|
||||
.ExecuteCommandAsync() > 0;
|
||||
return await UseTranAsync(async () =>
|
||||
{
|
||||
await messagePublishService.PublishBatchAsync(publishMessages);
|
||||
return await base.Updateable()
|
||||
.SetColumns(s => s.Status, JournalStatusEnum.Published)
|
||||
.SetColumns(s => s.UpdatedAt, DateTime.Now)
|
||||
.Where(w => w.Id == id)
|
||||
.ExecuteCommandAsync() > 0;
|
||||
});
|
||||
|
||||
static (int First, int Second, int Third, int Fourth) ParseTaskNo(string? no)
|
||||
{
|
||||
|
||||
112
QYZH.InteractiveMagazine.Service/MessagePublishService.cs
Normal file
112
QYZH.InteractiveMagazine.Service/MessagePublishService.cs
Normal file
@ -0,0 +1,112 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using QYZH.InteractiveMagazine.Infrastructure.RabbitMQ;
|
||||
using QYZH.InteractiveMagazine.IService;
|
||||
using QYZH.InteractiveMagazine.Models.Common;
|
||||
using QYZH.InteractiveMagazine.Models.Dto;
|
||||
using QYZH.InteractiveMagazine.Models.Dto.RabbitMQ;
|
||||
using QYZH.InteractiveMagazine.Models.Entity;
|
||||
using QYZH.InteractiveMagazine.Models.Enum;
|
||||
using QYZH.InteractiveMagazine.Repository;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace QYZH.InteractiveMagazine.Service;
|
||||
|
||||
/// <summary>
|
||||
/// MQ消息发布服务。
|
||||
/// </summary>
|
||||
public class MessagePublishService(
|
||||
IRabbitMQService rabbitMQService,
|
||||
ILogger<MessagePublishService> logger) : BaseRepository<MessageOutbox>, IMessagePublishService
|
||||
{
|
||||
/// <summary>
|
||||
/// 可靠发布消息,默认写入Outbox。
|
||||
/// </summary>
|
||||
public async Task<MessagePublishResult> PublishAsync<T>(MessagePublishInput<T> input, CancellationToken cancellationToken = default)
|
||||
{
|
||||
ValidateInput(input);
|
||||
var message = BuildOutboxMessage(input);
|
||||
await Context.Insertable(message).ExecuteCommandAsync(cancellationToken);
|
||||
return new MessagePublishResult
|
||||
{
|
||||
Success = true,
|
||||
OutboxIds = [message.Id],
|
||||
Message = "消息已写入Outbox"
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 批量可靠发布消息,默认写入Outbox。
|
||||
/// </summary>
|
||||
public async Task<MessagePublishResult> PublishBatchAsync<T>(IEnumerable<MessagePublishInput<T>> inputs, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var inputList = inputs.ToList();
|
||||
BusinessException.ThrowIf(inputList.Count == 0, "消息发布列表不能为空", ResultCode.BAD_REQUEST);
|
||||
inputList.ForEach(ValidateInput);
|
||||
|
||||
var messages = inputList.Select(BuildOutboxMessage).ToList();
|
||||
await Context.Insertable(messages).ExecuteCommandAsync(cancellationToken);
|
||||
return new MessagePublishResult
|
||||
{
|
||||
Success = true,
|
||||
OutboxIds = messages.Select(x => x.Id).ToList(),
|
||||
Message = "消息已批量写入Outbox"
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 直接发布消息,不写Outbox。
|
||||
/// </summary>
|
||||
public async Task<MessagePublishResult> PublishDirectAsync<T>(MessagePublishInput<T> input, CancellationToken cancellationToken = default)
|
||||
{
|
||||
ValidateInput(input);
|
||||
var sent = await rabbitMQService.SendAsync(new RabbitMQSendParam
|
||||
{
|
||||
Exchange = input.Exchange,
|
||||
Queue = input.Queue,
|
||||
RoutingKey = input.RoutingKey,
|
||||
Data = input.Data!
|
||||
}, cancellationToken);
|
||||
|
||||
if (!sent)
|
||||
{
|
||||
logger.LogError("MQ直接发布失败,Exchange: {Exchange}, Queue: {Queue}, RoutingKey: {RoutingKey}, BusinessType: {BusinessType}, BusinessId: {BusinessId}",
|
||||
input.Exchange, input.Queue, input.RoutingKey, input.BusinessType, input.BusinessId);
|
||||
throw new BusinessException("MQ消息发送失败", ResultCode.GLOBAL_ERROR);
|
||||
}
|
||||
|
||||
return new MessagePublishResult
|
||||
{
|
||||
Success = true,
|
||||
Message = "消息已直接发送"
|
||||
};
|
||||
}
|
||||
|
||||
private static MessageOutbox BuildOutboxMessage<T>(MessagePublishInput<T> input)
|
||||
{
|
||||
var now = DateTime.Now;
|
||||
return new MessageOutbox
|
||||
{
|
||||
Exchange = input.Exchange,
|
||||
Queue = input.Queue,
|
||||
RoutingKey = input.RoutingKey,
|
||||
Payload = JsonSerializer.Serialize(input.Data),
|
||||
BusinessType = input.BusinessType,
|
||||
BusinessId = input.BusinessId,
|
||||
Status = (int)MessageOutboxStatusEnum.Pending,
|
||||
CreatedBy = "System",
|
||||
CreatedAt = now,
|
||||
UpdatedBy = "System",
|
||||
UpdatedAt = now
|
||||
};
|
||||
}
|
||||
|
||||
private static void ValidateInput<T>(MessagePublishInput<T> input)
|
||||
{
|
||||
BusinessException.ThrowIf(string.IsNullOrWhiteSpace(input.Exchange), "MQ交换机不能为空", ResultCode.BAD_REQUEST);
|
||||
BusinessException.ThrowIf(string.IsNullOrWhiteSpace(input.Queue), "MQ队列不能为空", ResultCode.BAD_REQUEST);
|
||||
BusinessException.ThrowIf(string.IsNullOrWhiteSpace(input.RoutingKey), "MQ路由键不能为空", ResultCode.BAD_REQUEST);
|
||||
BusinessException.ThrowIf(string.IsNullOrWhiteSpace(input.BusinessType), "MQ业务类型不能为空", ResultCode.BAD_REQUEST);
|
||||
BusinessException.ThrowIf(input.BusinessId <= 0, "MQ业务ID无效", ResultCode.BAD_REQUEST);
|
||||
BusinessException.ThrowIf(input.Data == null, "MQ消息数据不能为空", ResultCode.BAD_REQUEST);
|
||||
}
|
||||
}
|
||||
@ -24,23 +24,41 @@ public class OperationLogService(
|
||||
/// 记录操作日志
|
||||
/// </summary>
|
||||
public async Task LogAsync(long operatorId, string operatorName, string actionType, string targetType, long targetId, string? targetName = null, string? detail = null, string? ipAddress = null)
|
||||
{
|
||||
await LogAsync(new OperationLogRecordInput
|
||||
{
|
||||
OperatorId = operatorId,
|
||||
OperatorName = operatorName,
|
||||
ActionType = actionType,
|
||||
TargetType = targetType,
|
||||
TargetId = targetId,
|
||||
TargetName = targetName,
|
||||
Detail = detail,
|
||||
IpAddress = ipAddress
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 记录操作日志
|
||||
/// </summary>
|
||||
public async Task LogAsync(OperationLogRecordInput input)
|
||||
{
|
||||
try
|
||||
{
|
||||
var log = new OperationLog
|
||||
{
|
||||
OperatorId = operatorId,
|
||||
OperatorName = operatorName,
|
||||
ActionType = actionType,
|
||||
TargetType = targetType,
|
||||
TargetId = targetId,
|
||||
TargetName = targetName,
|
||||
Detail = detail,
|
||||
IpAddress = ipAddress,
|
||||
OperatorId = input.OperatorId,
|
||||
OperatorName = input.OperatorName,
|
||||
ActionType = input.ActionType,
|
||||
TargetType = input.TargetType,
|
||||
TargetId = input.TargetId,
|
||||
TargetName = input.TargetName,
|
||||
Detail = input.Detail,
|
||||
IpAddress = input.IpAddress,
|
||||
IsDeleted = false,
|
||||
CreatedBy = operatorName,
|
||||
CreatedBy = input.OperatorName,
|
||||
CreatedAt = DateTime.Now,
|
||||
UpdatedBy = operatorName,
|
||||
UpdatedBy = input.OperatorName,
|
||||
UpdatedAt = DateTime.Now
|
||||
};
|
||||
|
||||
@ -48,12 +66,12 @@ public class OperationLogService(
|
||||
|
||||
logger.LogInformation(
|
||||
"记录操作日志,Operator: {Operator}, Action: {Action}, Target: {TargetType}/{TargetId}",
|
||||
operatorName, actionType, targetType, targetId);
|
||||
input.OperatorName, input.ActionType, input.TargetType, input.TargetId);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// 日志记录不应影响主业务流程
|
||||
logger.LogError(ex, "记录操作日志失败,Operator: {Operator}, Action: {Action}", operatorName, actionType);
|
||||
logger.LogError(ex, "记录操作日志失败,Operator: {Operator}, Action: {Action}", input.OperatorName, input.ActionType);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -6,6 +6,7 @@ using QYZH.InteractiveMagazine.Models.Dto.Points;
|
||||
using QYZH.InteractiveMagazine.Models.Entity;
|
||||
using QYZH.InteractiveMagazine.Models.Enum;
|
||||
using QYZH.InteractiveMagazine.Repository;
|
||||
using SqlSugar;
|
||||
|
||||
namespace QYZH.InteractiveMagazine.Service;
|
||||
|
||||
@ -73,7 +74,18 @@ public class PointsService(
|
||||
if (input.Amount <= 0)
|
||||
throw new BusinessException("增加积分数量必须大于0", ResultCode.BAD_REQUEST);
|
||||
|
||||
// 查询用户当前积分
|
||||
var now = DateTime.Now;
|
||||
|
||||
// 原子增加积分,避免并发写回旧余额覆盖新余额
|
||||
var affectedRows = await Context.Updateable<Users>()
|
||||
.SetColumns(u => u.Points == u.Points + input.Amount)
|
||||
.SetColumns(u => u.UpdatedAt == now)
|
||||
.Where(u => u.Id == input.UserId && !u.IsDeleted)
|
||||
.ExecuteCommandAsync();
|
||||
|
||||
if (affectedRows <= 0)
|
||||
throw new BusinessException("用户不存在", ResultCode.NOT_FOUND);
|
||||
|
||||
var user = await Context.Queryable<Users>()
|
||||
.Where(u => u.Id == input.UserId && !u.IsDeleted)
|
||||
.FirstAsync();
|
||||
@ -81,15 +93,8 @@ public class PointsService(
|
||||
if (user == null)
|
||||
throw new BusinessException("用户不存在", ResultCode.NOT_FOUND);
|
||||
|
||||
var previousBalance = user.Points;
|
||||
var newBalance = previousBalance + input.Amount;
|
||||
|
||||
// 更新用户积分
|
||||
await Context.Updateable<Users>()
|
||||
.SetColumns(u => u.Points == newBalance)
|
||||
.SetColumns(u => u.UpdatedAt == DateTime.Now)
|
||||
.Where(u => u.Id == input.UserId && !u.IsDeleted)
|
||||
.ExecuteCommandAsync();
|
||||
var newBalance = user.Points;
|
||||
var previousBalance = newBalance - input.Amount;
|
||||
|
||||
// 插入积分流水记录
|
||||
var record = new PointsRecord
|
||||
@ -104,9 +109,9 @@ public class PointsService(
|
||||
Status = (int)PointsRecordStatusEnum.Success,
|
||||
IsDeleted = false,
|
||||
CreatedBy = input.OperatorName ?? user.Name ?? input.UserId.ToString(),
|
||||
CreatedAt = DateTime.Now,
|
||||
CreatedAt = now,
|
||||
UpdatedBy = input.OperatorName ?? user.Name ?? input.UserId.ToString(),
|
||||
UpdatedAt = DateTime.Now
|
||||
UpdatedAt = now
|
||||
};
|
||||
|
||||
var recordEntity = await InsertReturnEntityAsync(record);
|
||||
@ -131,7 +136,27 @@ public class PointsService(
|
||||
if (input.Amount <= 0)
|
||||
throw new BusinessException("扣除积分数量必须大于0", ResultCode.BAD_REQUEST);
|
||||
|
||||
// 查询用户当前积分
|
||||
var now = DateTime.Now;
|
||||
|
||||
// 带余额条件的原子扣减,避免并发扣减时超扣或覆盖余额
|
||||
var affectedRows = await Context.Updateable<Users>()
|
||||
.SetColumns(u => u.Points == u.Points - input.Amount)
|
||||
.SetColumns(u => u.UpdatedAt == now)
|
||||
.Where(u => u.Id == input.UserId && !u.IsDeleted && u.Points >= input.Amount)
|
||||
.ExecuteCommandAsync();
|
||||
|
||||
if (affectedRows <= 0)
|
||||
{
|
||||
var currentUser = await Context.Queryable<Users>()
|
||||
.Where(u => u.Id == input.UserId && !u.IsDeleted)
|
||||
.FirstAsync();
|
||||
|
||||
if (currentUser == null)
|
||||
throw new BusinessException("用户不存在", ResultCode.NOT_FOUND);
|
||||
|
||||
throw new BusinessException($"积分不足,需要 {input.Amount} 积分,当前余额 {currentUser.Points}", ResultCode.BAD_REQUEST);
|
||||
}
|
||||
|
||||
var user = await Context.Queryable<Users>()
|
||||
.Where(u => u.Id == input.UserId && !u.IsDeleted)
|
||||
.FirstAsync();
|
||||
@ -139,20 +164,8 @@ public class PointsService(
|
||||
if (user == null)
|
||||
throw new BusinessException("用户不存在", ResultCode.NOT_FOUND);
|
||||
|
||||
var previousBalance = user.Points;
|
||||
|
||||
// 余额不足校验
|
||||
if (previousBalance < input.Amount)
|
||||
throw new BusinessException($"积分不足,需要 {input.Amount} 积分,当前余额 {previousBalance}", ResultCode.BAD_REQUEST);
|
||||
|
||||
var newBalance = previousBalance - input.Amount;
|
||||
|
||||
// 更新用户积分
|
||||
await Context.Updateable<Users>()
|
||||
.SetColumns(u => u.Points == newBalance)
|
||||
.SetColumns(u => u.UpdatedAt == DateTime.Now)
|
||||
.Where(u => u.Id == input.UserId && !u.IsDeleted)
|
||||
.ExecuteCommandAsync();
|
||||
var newBalance = user.Points;
|
||||
var previousBalance = newBalance + input.Amount;
|
||||
|
||||
// 插入积分流水记录
|
||||
var record = new PointsRecord
|
||||
@ -167,9 +180,9 @@ public class PointsService(
|
||||
Status = (int)PointsRecordStatusEnum.Success,
|
||||
IsDeleted = false,
|
||||
CreatedBy = input.OperatorName ?? user.Name ?? input.UserId.ToString(),
|
||||
CreatedAt = DateTime.Now,
|
||||
CreatedAt = now,
|
||||
UpdatedBy = input.OperatorName ?? user.Name ?? input.UserId.ToString(),
|
||||
UpdatedAt = DateTime.Now
|
||||
UpdatedAt = now
|
||||
};
|
||||
|
||||
var recordEntity = await InsertReturnEntityAsync(record);
|
||||
@ -253,7 +266,7 @@ public class PointsService(
|
||||
.WhereIF(!string.IsNullOrEmpty(input.Status), r => r.Status.ToString() == input.Status)
|
||||
.OrderByDescending(r => r.CreatedAt);
|
||||
|
||||
var total = 0;
|
||||
RefAsync<int> total = 0;
|
||||
var records = await query
|
||||
.Select(r => new PointsRecordOutput
|
||||
{
|
||||
|
||||
@ -172,29 +172,18 @@ public class UserAnswerTaskService(
|
||||
|
||||
// ============================================
|
||||
// 【积分和成长值计算】
|
||||
// 1. totalGrowthPoints:从已完成作答记录中,按 JournalPageTaskGroupId 查询主任务(Id=GroupId)的 GrowthPoint
|
||||
// 2. totalPoints:统计该期刊下已完成任务的 Points 总数
|
||||
// totalGrowthPoints:当前期刊下所有主任务(Id == GroupId)的成长值总和
|
||||
// totalPoints:当前期刊下所有主任务(Id == GroupId)的积分总和
|
||||
// ============================================
|
||||
// 该期刊下用户已完成的作答记录
|
||||
// 【已完成判断逻辑】
|
||||
// Status: 0=未批阅,1=已批阅
|
||||
var completedAnswers = userAnswers
|
||||
.Where(a => a.JournalId == journal.Id && a.Status == (int)UserAnswerStatusEnum.Complete)
|
||||
.ToList();
|
||||
// 筛选当前期刊下的主任务:Id == GroupId
|
||||
|
||||
var allMainTasks = journalTasks.Where(t => t.Id == t.GroupId).ToList();
|
||||
|
||||
// 按 JournalPageTaskGroupId 去重,获取已完成的主任务 ID 列表
|
||||
var completedGroupIdsSet = completedAnswers.Select(a => a.JournalPageTaskGroupId).Distinct().ToList();
|
||||
// 累计成长值(所有主任务的 GrowthPoint 总和)
|
||||
var totalGrowthPoints = allMainTasks.Sum(t => t.GrowthPoint);
|
||||
|
||||
// 从主任务表查询这些 GroupId 对应的 GrowthPoint 和 Points
|
||||
var mainTasks = allTasks
|
||||
.Where(t => completedGroupIdsSet.Contains(t.GroupId) && t.Id == t.GroupId)
|
||||
.ToList();
|
||||
|
||||
// 累计成长值(从主任务的 GrowthPoint 累加)
|
||||
var totalGrowthPoints = mainTasks.Sum(t => t.GrowthPoint);
|
||||
|
||||
// 累计积分(从已完成任务的 Points 累加,每个 GroupId 只计算一次)
|
||||
var totalPoints = mainTasks.Sum(t => t.Points);
|
||||
// 累计积分(所有主任务的 Points 总和)
|
||||
var totalPoints = allMainTasks.Sum(t => t.Points);
|
||||
|
||||
// 【进度百分比计算】按篇数统计
|
||||
// 只计算有题目的页面,进度 = 已答篇数 / 有题目的总篇数 × 100
|
||||
|
||||
@ -5,6 +5,8 @@ using QYZH.InteractiveMagazine.Infrastructure.RabbitMQ;
|
||||
using QYZH.InteractiveMagazine.IService;
|
||||
using QYZH.InteractiveMagazine.Models.Common;
|
||||
using QYZH.InteractiveMagazine.Models.Dto;
|
||||
using QYZH.InteractiveMagazine.Models.Dto.Journal;
|
||||
using QYZH.InteractiveMagazine.Models.Dto.RabbitMQ;
|
||||
using QYZH.InteractiveMagazine.Models.Entity;
|
||||
using QYZH.InteractiveMagazine.Models.Enum;
|
||||
using QYZH.InteractiveMagazine.Repository;
|
||||
@ -21,7 +23,7 @@ public class UserJournalService(
|
||||
BaseRepository<Users> usersRepository,
|
||||
BaseRepository<Journal> journalRepository,
|
||||
ILogger<UserJournalService> logger,
|
||||
IRabbitMQService rabbitMqService,
|
||||
IMessagePublishService messagePublishService,
|
||||
OssService ossService,
|
||||
IPetService petService)
|
||||
: BaseRepository<UserJournal>, IUserJournalService
|
||||
@ -214,7 +216,7 @@ public class UserJournalService(
|
||||
}
|
||||
|
||||
var recordIds = records.Select(r => r.Id).ToList();
|
||||
var messageSent = await rabbitMqService.SendAsync(new RabbitMQSendParam
|
||||
var messageSent = (await messagePublishService.PublishAsync(new MessagePublishInput<GenerateUserJournalQrCodeMessage>
|
||||
{
|
||||
Exchange = JournalExchange,
|
||||
Queue = QrCodeGenerateQueue,
|
||||
@ -223,8 +225,10 @@ public class UserJournalService(
|
||||
{
|
||||
RecordIds = recordIds,
|
||||
OperatorName = operatorName
|
||||
}
|
||||
});
|
||||
},
|
||||
BusinessType = "UserJournalQrCodeGenerate",
|
||||
BusinessId = input.JournalId
|
||||
})).Success;
|
||||
|
||||
if (!messageSent)
|
||||
{
|
||||
@ -457,7 +461,7 @@ public class UserJournalService(
|
||||
{
|
||||
try
|
||||
{
|
||||
var messageSent = await rabbitMqService.SendAsync(new RabbitMQSendParam
|
||||
var messageSent = (await messagePublishService.PublishAsync(new MessagePublishInput<BindJournalMessage>
|
||||
{
|
||||
Exchange = JournalExchange,
|
||||
Queue = BindJournalQueue,
|
||||
@ -469,8 +473,10 @@ public class UserJournalService(
|
||||
StartTime = journal.StartTime,
|
||||
EndTime = journal.EndTime,
|
||||
UploadDomain = user.UploadDomain
|
||||
}
|
||||
});
|
||||
},
|
||||
BusinessType = "UserJournalBind",
|
||||
BusinessId = user.Id
|
||||
})).Success;
|
||||
|
||||
if (!messageSent)
|
||||
{
|
||||
|
||||
@ -252,11 +252,17 @@ public class UsersService(
|
||||
RecordId = result.RecordId
|
||||
});
|
||||
|
||||
await operationLogService.LogAsync(
|
||||
operatorId, operatorName,
|
||||
OperationLogActionType.ManualAddPoints,
|
||||
OperationLogTargetType.User,
|
||||
userId, user.Name, detail, ipAddress);
|
||||
await operationLogService.LogAsync(new OperationLogRecordInput
|
||||
{
|
||||
OperatorId = operatorId,
|
||||
OperatorName = operatorName,
|
||||
ActionType = OperationLogActionType.ManualAddPoints,
|
||||
TargetType = OperationLogTargetType.User,
|
||||
TargetId = userId,
|
||||
TargetName = user.Name,
|
||||
Detail = detail,
|
||||
IpAddress = ipAddress
|
||||
});
|
||||
|
||||
return new ManualPointsOutput
|
||||
{
|
||||
@ -302,11 +308,17 @@ public class UsersService(
|
||||
RecordId = result.RecordId
|
||||
});
|
||||
|
||||
await operationLogService.LogAsync(
|
||||
operatorId, operatorName,
|
||||
OperationLogActionType.ManualDeductPoints,
|
||||
OperationLogTargetType.User,
|
||||
userId, user.Name, detail, ipAddress);
|
||||
await operationLogService.LogAsync(new OperationLogRecordInput
|
||||
{
|
||||
OperatorId = operatorId,
|
||||
OperatorName = operatorName,
|
||||
ActionType = OperationLogActionType.ManualDeductPoints,
|
||||
TargetType = OperationLogTargetType.User,
|
||||
TargetId = userId,
|
||||
TargetName = user.Name,
|
||||
Detail = detail,
|
||||
IpAddress = ipAddress
|
||||
});
|
||||
|
||||
return new ManualPointsOutput
|
||||
{
|
||||
|
||||
@ -253,9 +253,9 @@ public class WxMallService(
|
||||
{
|
||||
// 已有同类物品,累加数量
|
||||
await exchangeRecordRepository.Context.Updateable<UserBag>()
|
||||
.SetColumns(b => b.Quantity == existingBag.Quantity + input.Quantity)
|
||||
.SetColumns(b => b.Quantity == b.Quantity + input.Quantity)
|
||||
.SetColumns(b => b.UpdatedAt == DateTime.Now)
|
||||
.Where(b => b.Id == existingBag.Id)
|
||||
.Where(b => b.Id == existingBag.Id && !b.IsDeleted && b.Status == (int)UserBagStatusEnum.Available)
|
||||
.ExecuteCommandAsync();
|
||||
}
|
||||
else
|
||||
|
||||
@ -26,7 +26,7 @@ public class UserAnswerTaskController : WeChatBaseController
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
private const long ConstUserId = 821752253853766;
|
||||
private const long ConstUserId = 821752253853766; //ConstUserId;//
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前用户的期刊学习进度列表
|
||||
@ -275,7 +275,7 @@ public class UserAnswerTaskController : WeChatBaseController
|
||||
{
|
||||
try
|
||||
{
|
||||
var userId = ConstUserId;//GetCurrentUserId();
|
||||
var userId = GetCurrentUserId();
|
||||
if (userId == 0)
|
||||
{
|
||||
return BaseResponse<CatalogTreeOutput>.Fail(ResultCode.DENY, "未获取到用户信息");
|
||||
|
||||
@ -26,7 +26,8 @@ var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
builder.Configuration.AddJsonFile("medal-rule-config.json", optional: false, reloadOnChange: true);
|
||||
|
||||
YitIdHelper.SetIdGenerator(new IdGeneratorOptions { WorkerId = 1 });
|
||||
var snowflakeSettings = builder.Configuration.GetSection("SnowflakeSettings").Get<SnowflakeSettings>() ?? new SnowflakeSettings { WorkerId = 1 };
|
||||
YitIdHelper.SetIdGenerator(new IdGeneratorOptions { WorkerId = snowflakeSettings.WorkerId });
|
||||
|
||||
builder.UseAutofac();
|
||||
|
||||
@ -38,66 +39,14 @@ builder.InitSqlSugarDb(new IocConfig
|
||||
IsAutoCloseConnection = true,
|
||||
});
|
||||
|
||||
builder.Services.AddDataProtection()
|
||||
.PersistKeysToFileSystem(new DirectoryInfo(Path.Combine(Directory.GetCurrentDirectory(), "DataProtection")));
|
||||
|
||||
builder.Services.AddCSRedisCacheExtension(builder.Configuration.GetSection("RedisSettings"));
|
||||
builder.Services.AddRabbitMQ(builder.Configuration);
|
||||
|
||||
Log.Logger = new LoggerConfiguration()
|
||||
.ReadFrom.Configuration(builder.Configuration)
|
||||
.Enrich.FromLogContext()
|
||||
.CreateLogger();
|
||||
|
||||
builder.Host.UseSerilog();
|
||||
|
||||
builder.Services.AddControllers(options =>
|
||||
{
|
||||
options.SuppressImplicitRequiredAttributeForNonNullableReferenceTypes = true;
|
||||
options.Filters.Add<ModelValidActionFilterAttribute>();
|
||||
})
|
||||
.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.AddInteractiveMagazineApiDefaults();
|
||||
builder.Services.AddScoped(typeof(BaseRepository<>));
|
||||
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"]);
|
||||
});
|
||||
|
||||
builder.Services.AddSwaggerGen(option =>
|
||||
{
|
||||
|
||||
@ -9,6 +9,9 @@
|
||||
"ExpiryMinutes": 120,
|
||||
"JwtTokenExpiryDays": 30
|
||||
},
|
||||
"SnowflakeSettings": {
|
||||
"WorkerId": 1
|
||||
},
|
||||
"RedisSettings": {
|
||||
"ConnectionString": "192.168.20.150:16379,defaultDatabase=5",
|
||||
"Sentinels": [],
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using QYZH.InteractiveMagazine.Infrastructure.Middleware;
|
||||
using QYZH.InteractiveMagazine.IService;
|
||||
|
||||
using QYZH.InteractiveMagazine.Models.Common;
|
||||
@ -76,6 +77,7 @@ public class AdminController : BaseController
|
||||
/// </summary>
|
||||
/// <param name="input">修改密码输入</param>
|
||||
/// <returns>修改密码结果</returns>
|
||||
[OperationLog(OperationLogActionType.ChangePassword, OperationLogTargetType.AdminUser, UseOperatorAsTargetId = true, LogArguments = false)]
|
||||
[HttpPost("changePassword")]
|
||||
public async Task<BaseResponse<object>> ChangePasswordAsync([FromBody] ChangePasswordInput input)
|
||||
{
|
||||
@ -94,6 +96,7 @@ public class AdminController : BaseController
|
||||
/// </summary>
|
||||
/// <param name="input">管理员输入</param>
|
||||
/// <returns>创建的管理员信息</returns>
|
||||
[OperationLog(OperationLogActionType.Create, OperationLogTargetType.AdminUser, TargetNameProperty = "UserName")]
|
||||
[HttpPost("users")]
|
||||
public async Task<BaseResponse<AdminUserOutput>> CreateUserAsync([FromBody] AdminUserInput input)
|
||||
{
|
||||
@ -120,6 +123,7 @@ public class AdminController : BaseController
|
||||
/// <param name="id">管理员ID</param>
|
||||
/// <param name="input">管理员输入</param>
|
||||
/// <returns>更新后的管理员信息</returns>
|
||||
[OperationLog(OperationLogActionType.Update, OperationLogTargetType.AdminUser, TargetNameProperty = "UserName")]
|
||||
[HttpPut("users/{id}")]
|
||||
public async Task<BaseResponse<AdminUserOutput>> UpdateUserAsync(long id, [FromBody] AdminUserInput input)
|
||||
{
|
||||
@ -145,6 +149,7 @@ public class AdminController : BaseController
|
||||
/// </summary>
|
||||
/// <param name="id">管理员ID</param>
|
||||
/// <returns>操作结果</returns>
|
||||
[OperationLog(OperationLogActionType.Delete, OperationLogTargetType.AdminUser)]
|
||||
[HttpDelete("users/{id}")]
|
||||
public async Task<BaseResponse<object>> DeleteUserAsync(long id)
|
||||
{
|
||||
@ -220,6 +225,7 @@ public class AdminController : BaseController
|
||||
/// </summary>
|
||||
/// <param name="id">管理员ID</param>
|
||||
/// <returns>操作结果</returns>
|
||||
[OperationLog(OperationLogActionType.StatusChange, OperationLogTargetType.AdminUser)]
|
||||
[HttpPut("users/{id}/status")]
|
||||
public async Task<BaseResponse<object>> ToggleUserStatusAsync(long id)
|
||||
{
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using QYZH.InteractiveMagazine.Infrastructure.Middleware;
|
||||
using QYZH.InteractiveMagazine.IService;
|
||||
using QYZH.InteractiveMagazine.Models.Common;
|
||||
using QYZH.InteractiveMagazine.Models.Dto;
|
||||
@ -28,6 +29,7 @@ public class AiBasePromptController : BaseController
|
||||
/// </summary>
|
||||
/// <param name="input">Prompt配置信息</param>
|
||||
/// <returns>创建的Prompt配置信息</returns>
|
||||
[OperationLog(OperationLogActionType.Create, OperationLogTargetType.AiBasePrompt)]
|
||||
[HttpPost]
|
||||
public async Task<BaseResponse<AiBasePromptOutput>> CreateAsync([FromBody] AiBasePromptInput input)
|
||||
{
|
||||
@ -54,6 +56,7 @@ public class AiBasePromptController : BaseController
|
||||
/// <param name="id">Prompt配置ID</param>
|
||||
/// <param name="input">Prompt配置信息</param>
|
||||
/// <returns>更新后的Prompt配置信息</returns>
|
||||
[OperationLog(OperationLogActionType.Update, OperationLogTargetType.AiBasePrompt)]
|
||||
[HttpPut("{id}")]
|
||||
public async Task<BaseResponse<AiBasePromptOutput>> UpdateAsync(long id, [FromBody] AiBasePromptInput input)
|
||||
{
|
||||
@ -79,6 +82,7 @@ public class AiBasePromptController : BaseController
|
||||
/// </summary>
|
||||
/// <param name="id">Prompt配置ID</param>
|
||||
/// <returns>操作结果</returns>
|
||||
[OperationLog(OperationLogActionType.Delete, OperationLogTargetType.AiBasePrompt)]
|
||||
[HttpDelete("{id}")]
|
||||
public async Task<BaseResponse<object>> DeleteAsync(long id)
|
||||
{
|
||||
@ -178,6 +182,7 @@ public class AiBasePromptController : BaseController
|
||||
/// </summary>
|
||||
/// <param name="id">Prompt配置ID</param>
|
||||
/// <returns>操作结果</returns>
|
||||
[OperationLog(OperationLogActionType.StatusChange, OperationLogTargetType.AiBasePrompt)]
|
||||
[HttpPut("{id}/toggle-status")]
|
||||
public async Task<BaseResponse<object>> ToggleStatusAsync(long id)
|
||||
{
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using QYZH.InteractiveMagazine.Infrastructure.Middleware;
|
||||
using QYZH.InteractiveMagazine.IService;
|
||||
using QYZH.InteractiveMagazine.Models.Dto;
|
||||
using QYZH.InteractiveMagazine.Models.Dto.Banner;
|
||||
@ -19,6 +20,7 @@ public class BannerController(IBannerService bannerService) : BaseController
|
||||
/// </summary>
|
||||
/// <param name="input">轮播图信息</param>
|
||||
/// <returns>创建后的轮播图信息</returns>
|
||||
[OperationLog(OperationLogActionType.Create, OperationLogTargetType.Banner)]
|
||||
[HttpPost]
|
||||
public async Task<BaseResponse<BannerOutput>> CreateAsync([FromBody] BannerInput input)
|
||||
{
|
||||
@ -32,6 +34,7 @@ public class BannerController(IBannerService bannerService) : BaseController
|
||||
/// <param name="id">轮播图ID</param>
|
||||
/// <param name="input">轮播图信息</param>
|
||||
/// <returns>更新后的轮播图信息</returns>
|
||||
[OperationLog(OperationLogActionType.Update, OperationLogTargetType.Banner)]
|
||||
[HttpPut("{id}")]
|
||||
public async Task<BaseResponse<BannerOutput>> UpdateAsync(long id, [FromBody] BannerInput input)
|
||||
{
|
||||
@ -44,6 +47,7 @@ public class BannerController(IBannerService bannerService) : BaseController
|
||||
/// </summary>
|
||||
/// <param name="id">轮播图ID</param>
|
||||
/// <returns>操作结果</returns>
|
||||
[OperationLog(OperationLogActionType.Delete, OperationLogTargetType.Banner)]
|
||||
[HttpDelete("{id}")]
|
||||
public async Task<BaseResponse<object>> DeleteAsync(long id)
|
||||
{
|
||||
@ -80,6 +84,7 @@ public class BannerController(IBannerService bannerService) : BaseController
|
||||
/// </summary>
|
||||
/// <param name="id">轮播图ID</param>
|
||||
/// <returns>操作结果</returns>
|
||||
[OperationLog(OperationLogActionType.StatusChange, OperationLogTargetType.Banner)]
|
||||
[HttpPut("{id}/status")]
|
||||
public async Task<BaseResponse<bool>> UpdateStatusAsync(long id)
|
||||
{
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using QYZH.InteractiveMagazine.Infrastructure.Middleware;
|
||||
using QYZH.InteractiveMagazine.IService;
|
||||
using QYZH.InteractiveMagazine.Models.Common;
|
||||
using QYZH.InteractiveMagazine.Models.Dto;
|
||||
@ -29,6 +30,7 @@ public class CheckInConfigController : BaseController
|
||||
/// </summary>
|
||||
/// <param name="input">签到配置信息</param>
|
||||
/// <returns>创建的签到配置信息</returns>
|
||||
[OperationLog(OperationLogActionType.Create, OperationLogTargetType.CheckInConfig)]
|
||||
[HttpPost]
|
||||
public async Task<BaseResponse<CheckInConfigOutput>> CreateAsync([FromBody] CheckInConfigInput input)
|
||||
{
|
||||
@ -55,6 +57,7 @@ public class CheckInConfigController : BaseController
|
||||
/// <param name="id">签到配置ID</param>
|
||||
/// <param name="input">签到配置信息</param>
|
||||
/// <returns>更新后的签到配置信息</returns>
|
||||
[OperationLog(OperationLogActionType.Update, OperationLogTargetType.CheckInConfig)]
|
||||
[HttpPut("{id}")]
|
||||
public async Task<BaseResponse<CheckInConfigOutput>> UpdateAsync(long id, [FromBody] CheckInConfigInput input)
|
||||
{
|
||||
@ -80,6 +83,7 @@ public class CheckInConfigController : BaseController
|
||||
/// </summary>
|
||||
/// <param name="id">签到配置ID</param>
|
||||
/// <returns>操作结果</returns>
|
||||
[OperationLog(OperationLogActionType.Delete, OperationLogTargetType.CheckInConfig)]
|
||||
[HttpDelete("{id}")]
|
||||
public async Task<BaseResponse<object>> DeleteAsync(long id)
|
||||
{
|
||||
@ -155,6 +159,7 @@ public class CheckInConfigController : BaseController
|
||||
/// </summary>
|
||||
/// <param name="id">签到配置ID</param>
|
||||
/// <returns>操作结果</returns>
|
||||
[OperationLog(OperationLogActionType.StatusChange, OperationLogTargetType.CheckInConfig)]
|
||||
[HttpPut("{id}/status")]
|
||||
public async Task<BaseResponse<bool>> UpdateStatusAsync(long id)
|
||||
{
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using QYZH.InteractiveMagazine.Infrastructure.Middleware;
|
||||
using QYZH.InteractiveMagazine.IService;
|
||||
using QYZH.InteractiveMagazine.Models.Common;
|
||||
using QYZH.InteractiveMagazine.Models.Dto;
|
||||
@ -72,6 +73,7 @@ public class CommunityMessageController : BaseController
|
||||
/// <summary>
|
||||
/// 删除消息
|
||||
/// </summary>
|
||||
[OperationLog(OperationLogActionType.Delete, OperationLogTargetType.CommunityMessage)]
|
||||
[HttpDelete("{id}")]
|
||||
public async Task<BaseResponse<object>> DeleteAsync(long id)
|
||||
{
|
||||
@ -95,6 +97,7 @@ public class CommunityMessageController : BaseController
|
||||
/// <summary>
|
||||
/// 冻结/解冻消息
|
||||
/// </summary>
|
||||
[OperationLog(OperationLogActionType.StatusChange, OperationLogTargetType.CommunityMessage)]
|
||||
[HttpPut("{id}/freeze")]
|
||||
public async Task<BaseResponse<object>> FreezeAsync(long id, [FromBody] AdminFreezeInput input)
|
||||
{
|
||||
@ -118,6 +121,7 @@ public class CommunityMessageController : BaseController
|
||||
/// <summary>
|
||||
/// 设置/取消精选
|
||||
/// </summary>
|
||||
[OperationLog(OperationLogActionType.StatusChange, OperationLogTargetType.CommunityMessage)]
|
||||
[HttpPut("{id}/featured")]
|
||||
public async Task<BaseResponse<object>> SetFeaturedAsync(long id, [FromBody] AdminSetFeaturedInput input)
|
||||
{
|
||||
@ -141,6 +145,7 @@ public class CommunityMessageController : BaseController
|
||||
/// <summary>
|
||||
/// 设置排序权重
|
||||
/// </summary>
|
||||
[OperationLog(OperationLogActionType.Update, OperationLogTargetType.CommunityMessage)]
|
||||
[HttpPut("{id}/sort")]
|
||||
public async Task<BaseResponse<object>> SetSortOrderAsync(long id, [FromBody] AdminSetSortOrderInput input)
|
||||
{
|
||||
@ -164,6 +169,7 @@ public class CommunityMessageController : BaseController
|
||||
/// <summary>
|
||||
/// 批量发布消息
|
||||
/// </summary>
|
||||
[OperationLog(OperationLogActionType.StatusChange, OperationLogTargetType.CommunityMessage, TargetIdRouteKey = null)]
|
||||
[HttpPost("batch-publish")]
|
||||
public async Task<BaseResponse<object>> BatchPublishAsync([FromBody] AdminBatchPublishInput input)
|
||||
{
|
||||
|
||||
@ -6,6 +6,7 @@ using QYZH.InteractiveMagazine.Models.Enum;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using MiniExcelLibs;
|
||||
using QYZH.InteractiveMagazine.Infrastructure.Middleware;
|
||||
using QYZH.InteractiveMagazine.Infrastructure.OSS;
|
||||
using QYZH.InteractiveMagazine.IService;
|
||||
using QYZH.InteractiveMagazine.Models.Common;
|
||||
@ -76,6 +77,7 @@ namespace QYZH.InteractiveMagazine.WebApi.Controllers
|
||||
/// </summary>
|
||||
/// <param name="dto">创建杂志</param>
|
||||
/// <returns></returns>
|
||||
[OperationLog(OperationLogActionType.Create, OperationLogTargetType.Journal)]
|
||||
[HttpPost, Route("journal/add")]
|
||||
public async Task<BaseResponse<long>> AddAsync([FromBody] JournalAddDto dto)
|
||||
{
|
||||
@ -88,6 +90,7 @@ namespace QYZH.InteractiveMagazine.WebApi.Controllers
|
||||
/// </summary>
|
||||
/// <param name="dto">书籍对象</param>
|
||||
/// <returns></returns>
|
||||
[OperationLog(OperationLogActionType.Update, OperationLogTargetType.Journal, TargetIdArgumentName = "dto")]
|
||||
[HttpPost, Route("journal/update")]
|
||||
public async Task<BaseResponse> Edit([FromBody] JournalEditDto dto)
|
||||
{
|
||||
@ -100,6 +103,7 @@ namespace QYZH.InteractiveMagazine.WebApi.Controllers
|
||||
/// </summary>
|
||||
/// <param name="ids"></param>
|
||||
/// <returns></returns>
|
||||
[OperationLog(OperationLogActionType.Delete, OperationLogTargetType.Journal, TargetIdRouteKey = null)]
|
||||
[HttpPost, Route("journal/delete")]
|
||||
public async Task<BaseResponse<bool>> DeleteAsync([Required][FromBody] List<long> ids)
|
||||
{
|
||||
@ -111,6 +115,7 @@ namespace QYZH.InteractiveMagazine.WebApi.Controllers
|
||||
/// 书籍起始页
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[OperationLog(OperationLogActionType.Update, OperationLogTargetType.Journal)]
|
||||
[HttpPost, Route("journal/startpage")]
|
||||
public async Task<BaseResponse<bool>> StartPageAsync([FromQuery]long id, [FromQuery] int index)
|
||||
{
|
||||
@ -122,6 +127,7 @@ namespace QYZH.InteractiveMagazine.WebApi.Controllers
|
||||
/// 书籍归档
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[OperationLog(OperationLogActionType.StatusChange, OperationLogTargetType.Journal)]
|
||||
[HttpPost, Route("journal/rchive/{id:long}")]
|
||||
public async Task<BaseResponse<bool>> Archive(long id)
|
||||
{
|
||||
@ -133,6 +139,7 @@ namespace QYZH.InteractiveMagazine.WebApi.Controllers
|
||||
/// 书籍废弃
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[OperationLog(OperationLogActionType.StatusChange, OperationLogTargetType.Journal)]
|
||||
[HttpPost]
|
||||
[HttpPost, Route("journal/abandon/{id:long}")]
|
||||
public async Task<BaseResponse<bool>> Abandon(long id)
|
||||
@ -145,6 +152,7 @@ namespace QYZH.InteractiveMagazine.WebApi.Controllers
|
||||
/// 书籍发布
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[OperationLog(OperationLogActionType.StatusChange, OperationLogTargetType.Journal)]
|
||||
[HttpPost, Route("journal/publish/{id:long}")]
|
||||
public async Task<BaseResponse<bool>> Publish(long id)
|
||||
{
|
||||
@ -156,6 +164,7 @@ namespace QYZH.InteractiveMagazine.WebApi.Controllers
|
||||
/// 书籍铺码
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[OperationLog(OperationLogActionType.Update, OperationLogTargetType.Journal)]
|
||||
[HttpPost, Route("journal/printcode/{id:long}")]
|
||||
public async Task<BaseResponse<DotMatrixOutput>> PrintCodeAsync(long id)
|
||||
{
|
||||
@ -168,6 +177,7 @@ namespace QYZH.InteractiveMagazine.WebApi.Controllers
|
||||
/// <param name="input"></param>
|
||||
/// <param name="file"></param>
|
||||
/// <returns></returns>
|
||||
[OperationLog(OperationLogActionType.Update, OperationLogTargetType.Journal, TargetIdArgumentName = "input")]
|
||||
[HttpPost, Route("journal/resultreport")]
|
||||
public async Task<BaseResponse<bool>> GenerateAsync([FromBody] DotMatrixNoteJournalReportInput input, [FromForm] IFormFile? file)
|
||||
{
|
||||
@ -185,6 +195,7 @@ namespace QYZH.InteractiveMagazine.WebApi.Controllers
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
[OperationLog(OperationLogActionType.Create, OperationLogTargetType.Journal)]
|
||||
[HttpPost, Route("journal/import")]
|
||||
public async Task<BaseResponse<long>> JournalImport(JournalImportDto input)
|
||||
{
|
||||
@ -266,6 +277,7 @@ namespace QYZH.InteractiveMagazine.WebApi.Controllers
|
||||
/// <param name="JournalId">file</param>
|
||||
/// <param name="file">file</param>
|
||||
/// <returns></returns>
|
||||
[OperationLog(OperationLogActionType.Create, OperationLogTargetType.JournalCatalog, TargetIdRouteKey = "JournalId")]
|
||||
[HttpPost, Route("catalog/import/{JournalId}")]
|
||||
public async Task<BaseResponse<bool>> ImportAsync(long JournalId, [FromForm(Name = "file")] IFormFile file)
|
||||
{
|
||||
@ -283,6 +295,7 @@ namespace QYZH.InteractiveMagazine.WebApi.Controllers
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
[OperationLog(OperationLogActionType.Create, OperationLogTargetType.JournalCatalog)]
|
||||
[HttpPost, Route("catalog/add")]
|
||||
public async Task<BaseResponse<long>> CatalogAdd(JournalCatalogInput input)
|
||||
{
|
||||
@ -294,6 +307,7 @@ namespace QYZH.InteractiveMagazine.WebApi.Controllers
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
[OperationLog(OperationLogActionType.Update, OperationLogTargetType.JournalCatalog, TargetIdArgumentName = "input")]
|
||||
[HttpPost, Route("catalog/update")]
|
||||
public async Task<BaseResponse<bool>> CatalogUpdate(JournalCatalogUpdateInput input)
|
||||
{
|
||||
@ -305,6 +319,7 @@ namespace QYZH.InteractiveMagazine.WebApi.Controllers
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <returns></returns>
|
||||
[OperationLog(OperationLogActionType.Delete, OperationLogTargetType.JournalCatalog)]
|
||||
[HttpPost, Route("catalog/delete/{id:long}")]
|
||||
public async Task<BaseResponse<bool>> CatalogDelete(long id)
|
||||
{
|
||||
@ -318,6 +333,7 @@ namespace QYZH.InteractiveMagazine.WebApi.Controllers
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
[OperationLog(OperationLogActionType.Update, OperationLogTargetType.JournalCatalog, TargetIdArgumentName = "input")]
|
||||
[HttpPost, Route("catalog/move")]
|
||||
public async Task<BaseResponse<bool>> CatalogMove(MoveInput input)
|
||||
{
|
||||
@ -333,6 +349,7 @@ namespace QYZH.InteractiveMagazine.WebApi.Controllers
|
||||
/// 书页新增
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[OperationLog(OperationLogActionType.Create, OperationLogTargetType.JournalPage)]
|
||||
[HttpPost, Route("page/add")]
|
||||
public async Task<BaseResponse<long>> PageAdd(JournalAddV2Input input)
|
||||
{
|
||||
@ -344,6 +361,7 @@ namespace QYZH.InteractiveMagazine.WebApi.Controllers
|
||||
/// 书页修改
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[OperationLog(OperationLogActionType.Update, OperationLogTargetType.JournalPage, TargetIdArgumentName = "input")]
|
||||
[HttpPost, Route("page/update")]
|
||||
public async Task<BaseResponse<bool>> PageUpdate(PageLayoutInput input)
|
||||
{
|
||||
@ -355,6 +373,7 @@ namespace QYZH.InteractiveMagazine.WebApi.Controllers
|
||||
/// 自动铺码
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[OperationLog(OperationLogActionType.Update, OperationLogTargetType.JournalPage, TargetIdRouteKey = "JournalId")]
|
||||
[HttpPost, Route("page/updatepageno/{JournalId:long}")]
|
||||
public async Task<BaseResponse<bool>> PageUpdatePageNo([Required(ErrorMessage = "书籍编号不允许为空")] long JournalId)
|
||||
{
|
||||
@ -418,6 +437,7 @@ namespace QYZH.InteractiveMagazine.WebApi.Controllers
|
||||
/// 书页问题新增
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[OperationLog(OperationLogActionType.Create, OperationLogTargetType.JournalPageTask)]
|
||||
[HttpPost, Route("page/task/add")]
|
||||
[ProducesResponseType(typeof(BaseResponse<long>), 200)]
|
||||
public async Task<BaseResponse<long>> PageTaskAdd(JournalPageTaskAddInput input)
|
||||
@ -430,6 +450,7 @@ namespace QYZH.InteractiveMagazine.WebApi.Controllers
|
||||
/// 书页问题更新
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[OperationLog(OperationLogActionType.Update, OperationLogTargetType.JournalPageTask, TargetIdArgumentName = "input")]
|
||||
[HttpPost, Route("page/task/update")]
|
||||
[ProducesResponseType(typeof(BaseResponse<bool>), 200)]
|
||||
public async Task<BaseResponse<bool>> PageTaskUpdate(JournalPageTaskUpdateInput input)
|
||||
@ -454,6 +475,7 @@ namespace QYZH.InteractiveMagazine.WebApi.Controllers
|
||||
/// 书页问题删除
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[OperationLog(OperationLogActionType.Delete, OperationLogTargetType.JournalPageTask)]
|
||||
[HttpPost, Route("page/task/delete/{id:long}")]
|
||||
public async Task<BaseResponse<bool>> PageTaskDelete(long id)
|
||||
{
|
||||
@ -465,6 +487,7 @@ namespace QYZH.InteractiveMagazine.WebApi.Controllers
|
||||
/// 书页问题补充
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[OperationLog(OperationLogActionType.Update, OperationLogTargetType.JournalPageTask, TargetIdArgumentName = "input")]
|
||||
[HttpPost, Route("page/task/complement")]
|
||||
public async Task<BaseResponse<bool>> ComplementAnsync(JournalPageTaskComplementInput input)
|
||||
{
|
||||
@ -476,6 +499,7 @@ namespace QYZH.InteractiveMagazine.WebApi.Controllers
|
||||
/// 答案新增
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[OperationLog(OperationLogActionType.Create, OperationLogTargetType.JournalTaskAnswer)]
|
||||
[HttpPost, Route("page/task/answer/add")]
|
||||
public async Task<BaseResponse<long>> AnswerAdd(JournalTaskAnswerAddInput input)
|
||||
{
|
||||
@ -487,6 +511,7 @@ namespace QYZH.InteractiveMagazine.WebApi.Controllers
|
||||
/// 答案更新
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[OperationLog(OperationLogActionType.Update, OperationLogTargetType.JournalTaskAnswer, TargetIdArgumentName = "input")]
|
||||
[HttpPost, Route("page/task/answer/update")]
|
||||
public async Task<BaseResponse<bool>> AnswerUpdate(JournalTaskAnswerUpdateInput input)
|
||||
{
|
||||
@ -498,6 +523,7 @@ namespace QYZH.InteractiveMagazine.WebApi.Controllers
|
||||
/// 答案删除
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[OperationLog(OperationLogActionType.Delete, OperationLogTargetType.JournalTaskAnswer)]
|
||||
[HttpPost, Route("page/task/answer/delete/{id:long}")]
|
||||
public async Task<BaseResponse<bool>> AnswerDelete(long id)
|
||||
{
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using QYZH.InteractiveMagazine.Infrastructure.Middleware;
|
||||
using QYZH.InteractiveMagazine.IService;
|
||||
using QYZH.InteractiveMagazine.Models.Dto;
|
||||
using QYZH.InteractiveMagazine.Models.Dto.Material;
|
||||
@ -19,6 +20,7 @@ public class MaterialController(IMaterialService materialService) : BaseControll
|
||||
/// </summary>
|
||||
/// <param name="input">资料信息</param>
|
||||
/// <returns>创建后的资料信息</returns>
|
||||
[OperationLog(OperationLogActionType.Create, OperationLogTargetType.Material)]
|
||||
[HttpPost]
|
||||
public async Task<BaseResponse<MaterialOutput>> CreateAsync([FromBody] MaterialInput input)
|
||||
{
|
||||
@ -32,6 +34,7 @@ public class MaterialController(IMaterialService materialService) : BaseControll
|
||||
/// <param name="id">资料ID</param>
|
||||
/// <param name="input">资料信息</param>
|
||||
/// <returns>更新后的资料信息</returns>
|
||||
[OperationLog(OperationLogActionType.Update, OperationLogTargetType.Material)]
|
||||
[HttpPut("{id}")]
|
||||
public async Task<BaseResponse<MaterialOutput>> UpdateAsync(long id, [FromBody] MaterialInput input)
|
||||
{
|
||||
@ -44,6 +47,7 @@ public class MaterialController(IMaterialService materialService) : BaseControll
|
||||
/// </summary>
|
||||
/// <param name="id">资料ID</param>
|
||||
/// <returns>操作结果</returns>
|
||||
[OperationLog(OperationLogActionType.Delete, OperationLogTargetType.Material)]
|
||||
[HttpDelete("{id}")]
|
||||
public async Task<BaseResponse<object>> DeleteAsync(long id)
|
||||
{
|
||||
@ -80,6 +84,7 @@ public class MaterialController(IMaterialService materialService) : BaseControll
|
||||
/// </summary>
|
||||
/// <param name="id">资料ID</param>
|
||||
/// <returns>操作结果</returns>
|
||||
[OperationLog(OperationLogActionType.StatusChange, OperationLogTargetType.Material)]
|
||||
[HttpPut("{id}/status")]
|
||||
public async Task<BaseResponse<bool>> UpdateStatusAsync(long id)
|
||||
{
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using QYZH.InteractiveMagazine.Infrastructure.Middleware;
|
||||
using QYZH.InteractiveMagazine.IService;
|
||||
using QYZH.InteractiveMagazine.Models.Common;
|
||||
using QYZH.InteractiveMagazine.Models.Dto;
|
||||
@ -28,6 +29,7 @@ public class MedalController : BaseController
|
||||
/// </summary>
|
||||
/// <param name="input">勋章信息</param>
|
||||
/// <returns>创建的勋章信息</returns>
|
||||
[OperationLog(OperationLogActionType.Create, OperationLogTargetType.Medal)]
|
||||
[HttpPost]
|
||||
public async Task<BaseResponse<MedalOutput>> CreateAsync([FromBody] MedalInput input)
|
||||
{
|
||||
@ -54,6 +56,7 @@ public class MedalController : BaseController
|
||||
/// <param name="id">勋章ID</param>
|
||||
/// <param name="input">勋章信息</param>
|
||||
/// <returns>更新后的勋章信息</returns>
|
||||
[OperationLog(OperationLogActionType.Update, OperationLogTargetType.Medal)]
|
||||
[HttpPut("{id}")]
|
||||
public async Task<BaseResponse<MedalOutput>> UpdateAsync(long id, [FromBody] MedalInput input)
|
||||
{
|
||||
@ -79,6 +82,7 @@ public class MedalController : BaseController
|
||||
/// </summary>
|
||||
/// <param name="id">勋章ID</param>
|
||||
/// <returns>操作结果</returns>
|
||||
[OperationLog(OperationLogActionType.Delete, OperationLogTargetType.Medal)]
|
||||
[HttpDelete("{id}")]
|
||||
public async Task<BaseResponse<object>> DeleteAsync(long id)
|
||||
{
|
||||
@ -155,6 +159,7 @@ public class MedalController : BaseController
|
||||
/// <param name="id">勋章ID</param>
|
||||
/// <param name="status">状态: 0=禁用, 1=启用</param>
|
||||
/// <returns>操作结果</returns>
|
||||
[OperationLog(OperationLogActionType.StatusChange, OperationLogTargetType.Medal)]
|
||||
[HttpPut("{id}/status")]
|
||||
public async Task<BaseResponse<bool>> UpdateStatusAsync(long id)
|
||||
{
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using QYZH.InteractiveMagazine.Infrastructure.Middleware;
|
||||
using QYZH.InteractiveMagazine.IService;
|
||||
using QYZH.InteractiveMagazine.Models.Common;
|
||||
using QYZH.InteractiveMagazine.Models.Dto;
|
||||
@ -17,6 +18,7 @@ public class PermissionController(IAdminPermissionService adminPermissionService
|
||||
/// <summary>
|
||||
/// 创建菜单
|
||||
/// </summary>
|
||||
[OperationLog(OperationLogActionType.Create, OperationLogTargetType.AdminMenu)]
|
||||
[HttpPost("menus")]
|
||||
public async Task<BaseResponse<AdminMenuOutput>> CreateMenuAsync([FromBody] AdminMenuInput input)
|
||||
{
|
||||
@ -27,6 +29,7 @@ public class PermissionController(IAdminPermissionService adminPermissionService
|
||||
/// <summary>
|
||||
/// 更新菜单
|
||||
/// </summary>
|
||||
[OperationLog(OperationLogActionType.Update, OperationLogTargetType.AdminMenu)]
|
||||
[HttpPut("menus/{id}")]
|
||||
public async Task<BaseResponse<AdminMenuOutput>> UpdateMenuAsync(long id, [FromBody] AdminMenuInput input)
|
||||
{
|
||||
@ -37,6 +40,7 @@ public class PermissionController(IAdminPermissionService adminPermissionService
|
||||
/// <summary>
|
||||
/// 删除菜单
|
||||
/// </summary>
|
||||
[OperationLog(OperationLogActionType.Delete, OperationLogTargetType.AdminMenu)]
|
||||
[HttpDelete("menus/{id}")]
|
||||
public async Task<BaseResponse<object>> DeleteMenuAsync(long id)
|
||||
{
|
||||
@ -70,6 +74,7 @@ public class PermissionController(IAdminPermissionService adminPermissionService
|
||||
/// <summary>
|
||||
/// 创建角色
|
||||
/// </summary>
|
||||
[OperationLog(OperationLogActionType.Create, OperationLogTargetType.AdminRole)]
|
||||
[HttpPost("roles")]
|
||||
public async Task<BaseResponse<AdminRoleOutput>> CreateRoleAsync([FromBody] AdminRoleInput input)
|
||||
{
|
||||
@ -80,6 +85,7 @@ public class PermissionController(IAdminPermissionService adminPermissionService
|
||||
/// <summary>
|
||||
/// 更新角色
|
||||
/// </summary>
|
||||
[OperationLog(OperationLogActionType.Update, OperationLogTargetType.AdminRole)]
|
||||
[HttpPut("roles/{id}")]
|
||||
public async Task<BaseResponse<AdminRoleOutput>> UpdateRoleAsync(long id, [FromBody] AdminRoleInput input)
|
||||
{
|
||||
@ -90,6 +96,7 @@ public class PermissionController(IAdminPermissionService adminPermissionService
|
||||
/// <summary>
|
||||
/// 删除角色
|
||||
/// </summary>
|
||||
[OperationLog(OperationLogActionType.Delete, OperationLogTargetType.AdminRole)]
|
||||
[HttpDelete("roles/{id}")]
|
||||
public async Task<BaseResponse<object>> DeleteRoleAsync(long id)
|
||||
{
|
||||
@ -120,6 +127,7 @@ public class PermissionController(IAdminPermissionService adminPermissionService
|
||||
/// <summary>
|
||||
/// 分配角色菜单
|
||||
/// </summary>
|
||||
[OperationLog(OperationLogActionType.Assign, OperationLogTargetType.AdminRole, TargetIdRouteKey = "roleId")]
|
||||
[HttpPut("roles/{roleId}/menus")]
|
||||
public async Task<BaseResponse<object>> AssignRoleMenusAsync(long roleId, [FromBody] AssignRoleMenusInput input)
|
||||
{
|
||||
@ -130,6 +138,7 @@ public class PermissionController(IAdminPermissionService adminPermissionService
|
||||
/// <summary>
|
||||
/// 分配管理员角色
|
||||
/// </summary>
|
||||
[OperationLog(OperationLogActionType.Assign, OperationLogTargetType.AdminUser, TargetIdRouteKey = "adminUserId")]
|
||||
[HttpPut("users/{adminUserId}/roles")]
|
||||
public async Task<BaseResponse<object>> AssignAdminUserRolesAsync(long adminUserId, [FromBody] AssignAdminUserRolesInput input)
|
||||
{
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using QYZH.InteractiveMagazine.Infrastructure.Middleware;
|
||||
using QYZH.InteractiveMagazine.IService;
|
||||
using QYZH.InteractiveMagazine.Models.Common;
|
||||
using QYZH.InteractiveMagazine.Models.Dto;
|
||||
@ -29,6 +30,7 @@ public class PetManageController : BaseController
|
||||
/// <summary>
|
||||
/// 创建宠物模板
|
||||
/// </summary>
|
||||
[OperationLog(OperationLogActionType.Create, OperationLogTargetType.Pet)]
|
||||
[HttpPost]
|
||||
public async Task<BaseResponse<PetTemplateOutput>> CreateTemplateAsync([FromBody] PetTemplateInput input)
|
||||
{
|
||||
@ -52,6 +54,7 @@ public class PetManageController : BaseController
|
||||
/// <summary>
|
||||
/// 更新宠物模板
|
||||
/// </summary>
|
||||
[OperationLog(OperationLogActionType.Update, OperationLogTargetType.Pet)]
|
||||
[HttpPut("{id}")]
|
||||
public async Task<BaseResponse<PetTemplateOutput>> UpdateTemplateAsync(long id, [FromBody] PetTemplateInput input)
|
||||
{
|
||||
@ -75,6 +78,7 @@ public class PetManageController : BaseController
|
||||
/// <summary>
|
||||
/// 删除宠物模板
|
||||
/// </summary>
|
||||
[OperationLog(OperationLogActionType.Delete, OperationLogTargetType.Pet)]
|
||||
[HttpDelete("{id}")]
|
||||
public async Task<BaseResponse<object>> DeleteTemplateAsync(long id)
|
||||
{
|
||||
@ -144,6 +148,7 @@ public class PetManageController : BaseController
|
||||
/// <summary>
|
||||
/// 更新宠物模板状态(启用/禁用)
|
||||
/// </summary>
|
||||
[OperationLog(OperationLogActionType.StatusChange, OperationLogTargetType.Pet)]
|
||||
[HttpPut("{id}/status")]
|
||||
public async Task<BaseResponse<object>> UpdateTemplateStatusAsync(long id)
|
||||
{
|
||||
@ -169,6 +174,7 @@ public class PetManageController : BaseController
|
||||
/// <summary>
|
||||
/// 创建进化阶段
|
||||
/// </summary>
|
||||
[OperationLog(OperationLogActionType.Create, OperationLogTargetType.PetEvolution)]
|
||||
[HttpPost("evolution")]
|
||||
public async Task<BaseResponse<PetEvolutionOutput>> CreateEvolutionAsync([FromBody] PetEvolutionInput input)
|
||||
{
|
||||
@ -192,6 +198,7 @@ public class PetManageController : BaseController
|
||||
/// <summary>
|
||||
/// 更新进化阶段
|
||||
/// </summary>
|
||||
[OperationLog(OperationLogActionType.Update, OperationLogTargetType.PetEvolution)]
|
||||
[HttpPut("evolution/{id}")]
|
||||
public async Task<BaseResponse<PetEvolutionOutput>> UpdateEvolutionAsync(long id, [FromBody] PetEvolutionInput input)
|
||||
{
|
||||
@ -215,6 +222,7 @@ public class PetManageController : BaseController
|
||||
/// <summary>
|
||||
/// 删除进化阶段
|
||||
/// </summary>
|
||||
[OperationLog(OperationLogActionType.Delete, OperationLogTargetType.PetEvolution)]
|
||||
[HttpDelete("evolution/{id}")]
|
||||
public async Task<BaseResponse<object>> DeleteEvolutionAsync(long id)
|
||||
{
|
||||
@ -286,6 +294,7 @@ public class PetManageController : BaseController
|
||||
/// <summary>
|
||||
/// 创建皮肤
|
||||
/// </summary>
|
||||
[OperationLog(OperationLogActionType.Create, OperationLogTargetType.PetSkin)]
|
||||
[HttpPost("skin")]
|
||||
public async Task<BaseResponse<PetSkinOutput>> CreateSkinAsync([FromBody] PetSkinInput input)
|
||||
{
|
||||
@ -309,6 +318,7 @@ public class PetManageController : BaseController
|
||||
/// <summary>
|
||||
/// 更新皮肤
|
||||
/// </summary>
|
||||
[OperationLog(OperationLogActionType.Update, OperationLogTargetType.PetSkin)]
|
||||
[HttpPut("skin/{id}")]
|
||||
public async Task<BaseResponse<PetSkinOutput>> UpdateSkinAsync(long id, [FromBody] PetSkinInput input)
|
||||
{
|
||||
@ -332,6 +342,7 @@ public class PetManageController : BaseController
|
||||
/// <summary>
|
||||
/// 删除皮肤
|
||||
/// </summary>
|
||||
[OperationLog(OperationLogActionType.Delete, OperationLogTargetType.PetSkin)]
|
||||
[HttpDelete("skin/{id}")]
|
||||
public async Task<BaseResponse<object>> DeleteSkinAsync(long id)
|
||||
{
|
||||
@ -403,6 +414,7 @@ public class PetManageController : BaseController
|
||||
/// <summary>
|
||||
/// 创建皮肤图片
|
||||
/// </summary>
|
||||
[OperationLog(OperationLogActionType.Create, OperationLogTargetType.PetSkinImage)]
|
||||
[HttpPost("skin-image")]
|
||||
public async Task<BaseResponse<PetSkinImageOutput>> CreateSkinImageAsync([FromBody] PetSkinImageInput input)
|
||||
{
|
||||
@ -426,6 +438,7 @@ public class PetManageController : BaseController
|
||||
/// <summary>
|
||||
/// 更新皮肤图片
|
||||
/// </summary>
|
||||
[OperationLog(OperationLogActionType.Update, OperationLogTargetType.PetSkinImage)]
|
||||
[HttpPut("skin-image/{id}")]
|
||||
public async Task<BaseResponse<PetSkinImageOutput>> UpdateSkinImageAsync(long id, [FromBody] PetSkinImageInput input)
|
||||
{
|
||||
@ -449,6 +462,7 @@ public class PetManageController : BaseController
|
||||
/// <summary>
|
||||
/// 删除皮肤图片
|
||||
/// </summary>
|
||||
[OperationLog(OperationLogActionType.Delete, OperationLogTargetType.PetSkinImage)]
|
||||
[HttpDelete("skin-image/{id}")]
|
||||
public async Task<BaseResponse<object>> DeleteSkinImageAsync(long id)
|
||||
{
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using QYZH.InteractiveMagazine.Infrastructure.Middleware;
|
||||
using QYZH.InteractiveMagazine.IService;
|
||||
using QYZH.InteractiveMagazine.Models.Common;
|
||||
using QYZH.InteractiveMagazine.Models.Dto;
|
||||
@ -28,6 +29,7 @@ public class ProductController : BaseController
|
||||
/// </summary>
|
||||
/// <param name="input">商品信息</param>
|
||||
/// <returns>创建的商品信息</returns>
|
||||
[OperationLog(OperationLogActionType.Create, OperationLogTargetType.Product)]
|
||||
[HttpPost]
|
||||
public async Task<BaseResponse<ProductOutput>> CreateAsync([FromBody] ProductInput input)
|
||||
{
|
||||
@ -54,6 +56,7 @@ public class ProductController : BaseController
|
||||
/// <param name="id">商品ID</param>
|
||||
/// <param name="input">商品信息</param>
|
||||
/// <returns>更新后的商品信息</returns>
|
||||
[OperationLog(OperationLogActionType.Update, OperationLogTargetType.Product)]
|
||||
[HttpPut("{id}")]
|
||||
public async Task<BaseResponse<ProductOutput>> UpdateAsync(long id, [FromBody] ProductInput input)
|
||||
{
|
||||
@ -79,6 +82,7 @@ public class ProductController : BaseController
|
||||
/// </summary>
|
||||
/// <param name="id">商品ID</param>
|
||||
/// <returns>操作结果</returns>
|
||||
[OperationLog(OperationLogActionType.Delete, OperationLogTargetType.Product)]
|
||||
[HttpDelete("{id}")]
|
||||
public async Task<BaseResponse<object>> DeleteAsync(long id)
|
||||
{
|
||||
@ -154,6 +158,7 @@ public class ProductController : BaseController
|
||||
/// </summary>
|
||||
/// <param name="id">商品ID</param>
|
||||
/// <returns>操作结果</returns>
|
||||
[OperationLog(OperationLogActionType.StatusChange, OperationLogTargetType.Product)]
|
||||
[HttpPut("{id}/status")]
|
||||
public async Task<BaseResponse<object>> UpdateSaleStatusAsync(long id)
|
||||
{
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using QYZH.InteractiveMagazine.Infrastructure.Middleware;
|
||||
using QYZH.InteractiveMagazine.IService;
|
||||
using QYZH.InteractiveMagazine.Models.Dto;
|
||||
using QYZH.InteractiveMagazine.Models.Enum;
|
||||
@ -18,6 +19,7 @@ public class UserJournalQrCodeController(IUserJournalService userJournalService)
|
||||
/// </summary>
|
||||
/// <param name="input">生成输入</param>
|
||||
/// <returns>二维码记录</returns>
|
||||
[OperationLog(OperationLogActionType.Create, OperationLogTargetType.UserJournalQrCode)]
|
||||
[HttpPost("add")]
|
||||
public async Task<BaseResponse<CreateUserJournalQrCodeOutput>> AddAsync([FromBody] CreateUserJournalQrCodeInput input)
|
||||
{
|
||||
@ -66,6 +68,7 @@ public class UserJournalQrCodeController(IUserJournalService userJournalService)
|
||||
/// </summary>
|
||||
/// <param name="input">删除输入</param>
|
||||
/// <returns>是否成功</returns>
|
||||
[OperationLog(OperationLogActionType.Delete, OperationLogTargetType.UserJournalQrCode, TargetIdRouteKey = null)]
|
||||
[HttpPost("delete")]
|
||||
public async Task<BaseResponse<bool>> DeleteAsync([FromBody] DeleteUserJournalQrCodeInput input)
|
||||
{
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using QYZH.InteractiveMagazine.Infrastructure.Middleware;
|
||||
using QYZH.InteractiveMagazine.IService;
|
||||
|
||||
using QYZH.InteractiveMagazine.Models.Common;
|
||||
@ -82,6 +83,7 @@ public class UsersController : BaseController
|
||||
/// <summary>
|
||||
/// 更新用户状态
|
||||
/// </summary>
|
||||
[OperationLog(OperationLogActionType.StatusChange, OperationLogTargetType.User)]
|
||||
[HttpPut("{id}/status")]
|
||||
public async Task<BaseResponse> UpdateStatus(long id)
|
||||
{
|
||||
|
||||
@ -32,8 +32,8 @@ var builder = WebApplication.CreateBuilder(args);
|
||||
// 加载勋章规则独立配置文件
|
||||
builder.Configuration.AddJsonFile("medal-rule-config.json", optional: false, reloadOnChange: true);
|
||||
|
||||
// 初始化雪花ID生成器
|
||||
YitIdHelper.SetIdGenerator(new IdGeneratorOptions() { WorkerId = 1 });
|
||||
var snowflakeSettings = builder.Configuration.GetSection("SnowflakeSettings").Get<SnowflakeSettings>() ?? new SnowflakeSettings { WorkerId = 1 };
|
||||
YitIdHelper.SetIdGenerator(new IdGeneratorOptions { WorkerId = snowflakeSettings.WorkerId });
|
||||
// autofac注入 允许使用autofac作为DI容器
|
||||
builder.UseAutofac();
|
||||
|
||||
@ -47,14 +47,6 @@ builder.InitSqlSugarDb(new IocConfig()
|
||||
IsAutoCloseConnection = true,
|
||||
});
|
||||
|
||||
// 消除Error unprotecting the session cookie警告
|
||||
builder.Services.AddDataProtection()
|
||||
.PersistKeysToFileSystem(new DirectoryInfo(Directory.GetCurrentDirectory() + Path.DirectorySeparatorChar + "DataProtection"));
|
||||
//redis
|
||||
builder.Services.AddCSRedisCacheExtension(builder.Configuration.GetSection("RedisSettings"));
|
||||
|
||||
//MQ
|
||||
builder.Services.AddRabbitMQ(builder.Configuration);
|
||||
// 配置Serilog
|
||||
Log.Logger = new LoggerConfiguration()
|
||||
.ReadFrom.Configuration(builder.Configuration)
|
||||
@ -62,26 +54,8 @@ Log.Logger = new LoggerConfiguration()
|
||||
.CreateLogger();
|
||||
|
||||
builder.Host.UseSerilog();
|
||||
builder.Services.AddControllers(options =>
|
||||
{
|
||||
options.SuppressImplicitRequiredAttributeForNonNullableReferenceTypes = true;//Required 不作为必填
|
||||
options.Filters.Add<ModelValidActionFilterAttribute>();//添加自定义模型验证
|
||||
})
|
||||
.AddJsonOptions(options =>
|
||||
{
|
||||
// 配置 JSON 不区分大小写(支持 camelCase 和 PascalCase)
|
||||
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.AddInteractiveMagazineApiDefaults();
|
||||
|
||||
// 跨域配置
|
||||
builder.Services.AddDataProtection().UseEphemeralDataProtectionProvider();
|
||||
builder.AddCorsRegister();
|
||||
//builder.Services.AddSession();
|
||||
builder.Services.AddHttpClient();
|
||||
// 注册 Swagger 文档
|
||||
builder.Services.AddSwaggerGen(option =>
|
||||
{
|
||||
@ -148,52 +122,8 @@ builder.Services.AddSwaggerGen(option =>
|
||||
});
|
||||
});
|
||||
|
||||
builder.Services.AddInfrastructureServices(builder.Configuration, builder.Environment);
|
||||
|
||||
//注册 HttpContextAccessor
|
||||
builder.Services.AddHttpContextAccessor();
|
||||
builder.Services.AddScoped(typeof(BaseRepository<>));
|
||||
|
||||
// 注册勋章规则配置
|
||||
builder.Services.Configure<MedalRuleConfigSettings>(builder.Configuration.GetSection("MedalRuleConfig"));
|
||||
|
||||
// 添加CORS
|
||||
builder.Services.AddCors(options =>
|
||||
{
|
||||
options.AddPolicy("AllowAll", policy =>
|
||||
{
|
||||
policy.AllowAnyOrigin()
|
||||
.AllowAnyMethod()
|
||||
.AllowAnyHeader();
|
||||
});
|
||||
});
|
||||
builder.Services.AddSDKService(builder.Configuration);
|
||||
|
||||
#region 配置数据压缩选项
|
||||
|
||||
// 1.首先配置压缩选项的Options>
|
||||
builder.Services.Configure<BrotliCompressionProviderOptions>(options =>
|
||||
{
|
||||
options.Level = System.IO.Compression.CompressionLevel.Optimal;
|
||||
});
|
||||
builder.Services.Configure<GzipCompressionProviderOptions>(options =>
|
||||
{
|
||||
options.Level = System.IO.Compression.CompressionLevel.Fastest;
|
||||
});
|
||||
|
||||
// 2.在服务容器中注册响应压缩服务
|
||||
builder.Services.AddResponseCompression(options =>
|
||||
{
|
||||
// 可以在这里进行详细配置
|
||||
options.EnableForHttps = true; // 启用对HTTPS响应的压缩(请注意安全风险)
|
||||
options.Providers.Add<BrotliCompressionProvider>();
|
||||
options.Providers.Add<GzipCompressionProvider>();
|
||||
//指定哪些类型的响应应该被压缩。默认列表包含常见的文本类类型,如 text/html, text/css, application/javascript, application/json, text/plain等
|
||||
options.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat(["image/svg+xml", "application/json", "text/plain"]);
|
||||
});
|
||||
|
||||
#endregion
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
{
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user