From ffaf3255635fd3fea6d730bd17130de10069722b Mon Sep 17 00:00:00 2001 From: zzh <1368735060@qq.com> Date: Tue, 30 Jun 2026 11:23:37 +0800 Subject: [PATCH] fix(xhs): repair publish/task list SQL and RBAC menu seeds Co-authored-by: Cursor --- sql/diagnose_rbac_bad_guids.sql | 15 +++- sql/fix_rbac_bad_guids.sql | 9 +++ sql/fix_xhs_menu_parent.sql | 53 +++++++++++++ sql/seed_xhs_rbac_menu.sql | 11 ++- sql/seed_xhs_role_permissions.sql | 22 ++++-- .../Services/Xhs/XhsAnalysisService.cs | 29 +++++++ .../Services/Xhs/XhsPublishService.cs | 17 +--- .../Services/Xhs/XhsTaskService.cs | 20 ++--- .../Repositories/IXhsRepositories.cs | 17 ++++ src/Oms.SqlSugarCore/Xhs/XhsRepositories.cs | 79 +++++++++++++++++++ src/SimpleAdmin.Api/Auth/AuthService.cs | 6 +- 11 files changed, 238 insertions(+), 40 deletions(-) create mode 100644 sql/fix_xhs_menu_parent.sql diff --git a/sql/diagnose_rbac_bad_guids.sql b/sql/diagnose_rbac_bad_guids.sql index e4f4c54..4f848df 100644 --- a/sql/diagnose_rbac_bad_guids.sql +++ b/sql/diagnose_rbac_bad_guids.sql @@ -29,9 +29,14 @@ FROM `user_role` ur WHERE (ur.`UserId` IS NOT NULL AND ur.`UserId` NOT REGEXP @GUID_RE) OR (ur.`RoleId` IS NOT NULL AND ur.`RoleId` NOT REGEXP @GUID_RE); -SELECT 'role_permission bad RoleId' AS section, rp.`Id`, rp.`RoleId`, rp.`PermissionCode` +SELECT 'role_permission bad rows' AS section, rp.`Id`, rp.`RoleId`, rp.`PermissionCode`, rp.`CreateUserId` FROM `role_permission` rp -WHERE rp.`RoleId` IS NOT NULL AND rp.`RoleId` NOT REGEXP @GUID_RE; +WHERE (rp.`RoleId` IS NOT NULL AND rp.`RoleId` NOT REGEXP @GUID_RE) + OR (rp.`CreateUserId` IS NOT NULL AND CAST(rp.`CreateUserId` AS CHAR) NOT REGEXP @GUID_RE); + +SELECT 'user_role bad CreateUserId' AS section, ur.`Id`, ur.`UserId`, ur.`RoleId`, ur.`CreateUserId` +FROM `user_role` ur +WHERE ur.`CreateUserId` IS NOT NULL AND CAST(ur.`CreateUserId` AS CHAR) NOT REGEXP @GUID_RE; SELECT 'organization bad rows' AS section, o.`Id`, o.`Name`, o.`ParentId`, o.`LeaderId` FROM `organization` o @@ -43,3 +48,9 @@ WHERE (o.`ParentId` IS NOT NULL AND o.`ParentId` <> '' AND o.`ParentId` NOT REGE SELECT 'menu CreateUserId=0' AS hint, COUNT(*) AS cnt FROM `menu` WHERE CAST(`CreateUserId` AS CHAR) = '0'; SELECT 'menu UpdateUserId=0' AS hint, COUNT(*) AS cnt FROM `menu` WHERE CAST(`UpdateUserId` AS CHAR) = '0'; SELECT 'menu PId=0' AS hint, COUNT(*) AS cnt FROM `menu` WHERE CAST(`PId` AS CHAR) = '0'; + +SELECT 'role_permission CreateUserId=0' AS hint, COUNT(*) AS cnt +FROM `role_permission` WHERE CAST(`CreateUserId` AS CHAR) = '0'; + +SELECT 'user_role CreateUserId=0' AS hint, COUNT(*) AS cnt +FROM `user_role` WHERE CAST(`CreateUserId` AS CHAR) = '0'; diff --git a/sql/fix_rbac_bad_guids.sql b/sql/fix_rbac_bad_guids.sql index bc42348..02e2d78 100644 --- a/sql/fix_rbac_bad_guids.sql +++ b/sql/fix_rbac_bad_guids.sql @@ -83,4 +83,13 @@ UPDATE `organization` SET `LeaderId` = NULL WHERE `LeaderId` IS NOT NULL AND `LeaderId` <> '' AND `LeaderId` NOT REGEXP @GUID_RE; +-- ========== role_permission / user_role(登录加载权限时会读 CreateUserId GUID)========== +UPDATE `role_permission` +SET `CreateUserId` = @ADMIN_USER_ID +WHERE `CreateUserId` IS NOT NULL AND CAST(`CreateUserId` AS CHAR) NOT REGEXP @GUID_RE; + +UPDATE `user_role` +SET `CreateUserId` = @ADMIN_USER_ID +WHERE `CreateUserId` IS NOT NULL AND CAST(`CreateUserId` AS CHAR) NOT REGEXP @GUID_RE; + -- 修复后请再执行 diagnose_rbac_bad_guids.sql 确认无脏行 diff --git a/sql/fix_xhs_menu_parent.sql b/sql/fix_xhs_menu_parent.sql new file mode 100644 index 0000000..a42cbc6 --- /dev/null +++ b/sql/fix_xhs_menu_parent.sql @@ -0,0 +1,53 @@ +-- 修复小红书菜单层级与「启动服务」消失问题 +-- RBAC 库执行 +-- +-- 原因: +-- 1. 一级「小红书」PId 必须为 00000000-0000-0000-0000-000000000000 +-- 2. 一级目录 PermissionCode 必须为空字符串(列 NOT NULL,不可写 NULL;勿与「启动服务」同权限码) + +SET @ROOT_PID = '00000000-0000-0000-0000-000000000000'; +SET @XHS_PID = 'a1b2c3d4-e5f6-7890-abcd-ef1234567801'; + +-- 一级:小红书(目录,PermissionCode 空字符串) +UPDATE `menu` +SET `PId` = @ROOT_PID, + `PermissionCode` = '', + `Route` = '/xhs', + `Component` = NULL, + `Name` = '小红书', + `IsHidden` = 0, + `IsDeleted` = 0 +WHERE `Id` = @XHS_PID; + +-- 6 个子菜单挂到「小红书」下 +UPDATE `menu` +SET `PId` = @XHS_PID, + `IsHidden` = 0, + `IsDeleted` = 0 +WHERE `Id` IN ( + 'a1b2c3d4-e5f6-7890-abcd-ef1234567802', -- 启动服务 + 'a1b2c3d4-e5f6-7890-abcd-ef1234567803', -- 内容分析 + 'a1b2c3d4-e5f6-7890-abcd-ef1234567804', -- 爆款创作 + 'a1b2c3d4-e5f6-7890-abcd-ef1234567805', -- 发布记录 + 'a1b2c3d4-e5f6-7890-abcd-ef1234567806', -- 技能卡片 + 'a1b2c3d4-e5f6-7890-abcd-ef1234567807' -- 拟人设置 +); + +-- 诊断 +SELECT `Name`, `Route`, `PId`, `PermissionCode`, + CASE + WHEN `Id` = @XHS_PID AND `PId` = @ROOT_PID AND (`PermissionCode` = '' OR `PermissionCode` IS NULL) THEN 'OK' + WHEN `PId` = @XHS_PID THEN 'OK-child' + ELSE 'CHECK' + END AS status +FROM `menu` +WHERE `Id` IN ( + @XHS_PID, + 'a1b2c3d4-e5f6-7890-abcd-ef1234567802', + 'a1b2c3d4-e5f6-7890-abcd-ef1234567803', + 'a1b2c3d4-e5f6-7890-abcd-ef1234567804', + 'a1b2c3d4-e5f6-7890-abcd-ef1234567805', + 'a1b2c3d4-e5f6-7890-abcd-ef1234567806', + 'a1b2c3d4-e5f6-7890-abcd-ef1234567807' +) +ORDER BY `Order`; diff --git a/sql/seed_xhs_rbac_menu.sql b/sql/seed_xhs_rbac_menu.sql index 755135f..5b59481 100644 --- a/sql/seed_xhs_rbac_menu.sql +++ b/sql/seed_xhs_rbac_menu.sql @@ -15,7 +15,7 @@ SET @ADMIN_USER_ID = IFNULL( ); SET @ROOT_PID = '00000000-0000-0000-0000-000000000000'; --- 一级:小红书 +-- 一级:小红书(目录节点,PermissionCode 留空字符串;子菜单各自绑权限) INSERT INTO `menu` ( `Id`, `PId`, `Name`, `PermissionCode`, `Type`, `Icon`, `Route`, `Component`, `Order`, `IsHidden`, `IsCached`, `Description`, `IsDeleted`, @@ -24,7 +24,7 @@ INSERT INTO `menu` ( 'a1b2c3d4-e5f6-7890-abcd-ef1234567801', @ROOT_PID, '小红书', - 'Xhs.Agent.Query', + '', 1, 'icon-fire', '/xhs', @@ -34,6 +34,10 @@ INSERT INTO `menu` ( UUID(), @ADMIN_USER_ID, NOW(), @ADMIN_USER_ID, NOW() ) ON DUPLICATE KEY UPDATE `Name` = VALUES(`Name`), + `PId` = VALUES(`PId`), + `PermissionCode` = VALUES(`PermissionCode`), + `Route` = VALUES(`Route`), + `Component` = VALUES(`Component`), `CreateUserId` = VALUES(`CreateUserId`), `UpdateUserId` = VALUES(`UpdateUserId`); @@ -50,7 +54,10 @@ INSERT INTO `menu` ( ('a1b2c3d4-e5f6-7890-abcd-ef1234567807', 'a1b2c3d4-e5f6-7890-abcd-ef1234567801', '拟人设置', 'Xhs.HumanSettings.Save', 1, NULL, '/xhs/humanSettings', 'views/xhs/humanSettings/index', 6, 0, 1, NULL, 0, UUID(), @ADMIN_USER_ID, NOW(), @ADMIN_USER_ID, NOW()) ON DUPLICATE KEY UPDATE `Name` = VALUES(`Name`), + `PId` = VALUES(`PId`), `Route` = VALUES(`Route`), + `Component` = VALUES(`Component`), + `Order` = VALUES(`Order`), `CreateUserId` = VALUES(`CreateUserId`), `UpdateUserId` = VALUES(`UpdateUserId`); diff --git a/sql/seed_xhs_role_permissions.sql b/sql/seed_xhs_role_permissions.sql index ae3a923..41fd0ce 100644 --- a/sql/seed_xhs_role_permissions.sql +++ b/sql/seed_xhs_role_permissions.sql @@ -1,8 +1,19 @@ --- 为客户角色(Code=Customer)批量追加小红书权限(幂等,可重复执行) --- RBAC 库执行;role_permission.CreateUserId 为 INT,可写 0 +-- 为客户 / Admin 角色批量追加小红书权限(幂等,可重复执行) +-- RBAC 库执行 +-- ⚠️ 本环境 role_permission.CreateUserId 为 CHAR(36) GUID,不可写整数 0(登录会报 Guid: 0) + +SET @ADMIN_USER_ID = ( + SELECT `Id` FROM `user` + WHERE `Account` = 'admin' AND (`IsDeleted` = 0 OR `IsDeleted` IS NULL) + LIMIT 1 +); +SET @ADMIN_USER_ID = IFNULL( + @ADMIN_USER_ID, + '00000000-0000-0000-0000-000000000001' +); INSERT INTO `role_permission` (`Id`, `RoleId`, `PermissionCode`, `CreateUserId`, `CreateTime`) -SELECT UUID(), r.`Id`, p.`Code`, 0, NOW() +SELECT UUID(), r.`Id`, p.`Code`, @ADMIN_USER_ID, NOW() FROM `role` r CROSS JOIN ( SELECT 'Xhs.Agent.Query' AS `Code` UNION ALL @@ -24,9 +35,8 @@ WHERE r.`Code` = 'Customer' WHERE rp.`RoleId` = r.`Id` AND rp.`PermissionCode` = p.`Code` ); --- 超级管理员(可选,便于内测) INSERT INTO `role_permission` (`Id`, `RoleId`, `PermissionCode`, `CreateUserId`, `CreateTime`) -SELECT UUID(), r.`Id`, p.`Code`, 0, NOW() +SELECT UUID(), r.`Id`, p.`Code`, @ADMIN_USER_ID, NOW() FROM `role` r CROSS JOIN ( SELECT 'Xhs.Agent.Query' AS `Code` UNION ALL @@ -47,3 +57,5 @@ WHERE r.`Code` = 'Admin' SELECT 1 FROM `role_permission` rp WHERE rp.`RoleId` = r.`Id` AND rp.`PermissionCode` = p.`Code` ); + +-- 若曾用 CreateUserId=0 插入过,执行 fix_rbac_bad_guids.sql 修复脏行 diff --git a/src/Oms.Application/Services/Xhs/XhsAnalysisService.cs b/src/Oms.Application/Services/Xhs/XhsAnalysisService.cs index 239642e..c2f157e 100644 --- a/src/Oms.Application/Services/Xhs/XhsAnalysisService.cs +++ b/src/Oms.Application/Services/Xhs/XhsAnalysisService.cs @@ -8,6 +8,7 @@ using Oms.Domain.XhsAggregate.Consts; using Oms.Domain.XhsAggregate.Entities; using Oms.Domain.XhsAggregate.Repositories; using System.Linq.Expressions; +using System.Text.Json; namespace Oms.Application.Services.Xhs; @@ -237,16 +238,44 @@ public class XhsAnalysisService : ApplicationService XhsTaskTypeCodes.Browse, "my_account_feeds", new { }), + XhsAnalysisTypeCodes.FeedDetail => ( + XhsTaskTypeCodes.FeedDetail, + "get_feed_detail", + BuildFeedDetailArgs(input)), _ => throw new BusinessException($"不支持的 analysisType: {analysisType}") }; } + private static object BuildFeedDetailArgs(XhsAnalysisStartInput input) + { + var feedId = input.Keyword ?? string.Empty; + string? xsecToken = null; + var loadAllComments = false; + var limit = 20; + + if (input.Filters is JsonElement je && je.ValueKind == JsonValueKind.Object) + { + if (je.TryGetProperty("xsec_token", out var xt)) xsecToken = xt.GetString(); + if (je.TryGetProperty("load_all_comments", out var lac)) loadAllComments = lac.GetBoolean(); + if (je.TryGetProperty("limit", out var lim) && lim.TryGetInt32(out var n)) limit = n; + } + + return new + { + feed_id = feedId, + xsec_token = xsecToken ?? string.Empty, + load_all_comments = loadAllComments, + limit + }; + } + private static string BuildSessionName(string analysisType, string? keyword) => analysisType switch { XhsAnalysisTypeCodes.Search => $"搜索:{keyword}", XhsAnalysisTypeCodes.HomeFeed => "首页推荐", XhsAnalysisTypeCodes.MyFeeds => "我的笔记", + XhsAnalysisTypeCodes.FeedDetail => $"笔记详情:{keyword}", _ => analysisType }; diff --git a/src/Oms.Application/Services/Xhs/XhsPublishService.cs b/src/Oms.Application/Services/Xhs/XhsPublishService.cs index 85065ba..86e3355 100644 --- a/src/Oms.Application/Services/Xhs/XhsPublishService.cs +++ b/src/Oms.Application/Services/Xhs/XhsPublishService.cs @@ -7,7 +7,6 @@ using Oms.Domain.CustomersAggregate.Repositories; using Oms.Domain.XhsAggregate.Consts; using Oms.Domain.XhsAggregate.Entities; using Oms.Domain.XhsAggregate.Repositories; -using System.Linq.Expressions; namespace Oms.Application.Services.Xhs; @@ -24,20 +23,12 @@ public class XhsPublishService : ApplicationService var customerId = await OmsSessionHelper.ResolveListCustomerIdAsync(CustomerRepository, CurrentUser, null); var pageIndex = OmsDocHelper.NormalizePage(input.Page); var pageSize = OmsDocHelper.NormalizePageSize(input.PageSize); - var status = input.Status?.Trim(); - var keyword = input.Keyword?.Trim(); - - Expression> predicate = x => - (!customerId.HasValue || x.CustomerId == customerId.Value) && - (string.IsNullOrWhiteSpace(status) || x.Status == status) && - (string.IsNullOrWhiteSpace(keyword) || x.Title.Contains(keyword)); - - var items = await PublishRecordRepository.GetPagedListAsync( - predicate, + var (items, total) = await PublishRecordRepository.GetFilteredPagedListAsync( pageIndex, pageSize, - new Dictionary { [nameof(XhsPublishRecord.CreateTime)] = false }); - var total = await PublishRecordRepository.CountAsync(predicate); + customerId, + input.Status, + input.Keyword); return new OmsPageResult { diff --git a/src/Oms.Application/Services/Xhs/XhsTaskService.cs b/src/Oms.Application/Services/Xhs/XhsTaskService.cs index 602a44a..5a36c9d 100644 --- a/src/Oms.Application/Services/Xhs/XhsTaskService.cs +++ b/src/Oms.Application/Services/Xhs/XhsTaskService.cs @@ -7,7 +7,6 @@ using Oms.Domain.CustomersAggregate.Repositories; using Oms.Domain.XhsAggregate.Consts; using Oms.Domain.XhsAggregate.Entities; using Oms.Domain.XhsAggregate.Repositories; -using System.Linq.Expressions; namespace Oms.Application.Services.Xhs; @@ -89,22 +88,13 @@ public class XhsTaskService : ApplicationService var customerId = await OmsSessionHelper.ResolveListCustomerIdAsync(CustomerRepository, CurrentUser, null); var pageIndex = OmsDocHelper.NormalizePage(input.Page); var pageSize = OmsDocHelper.NormalizePageSize(input.PageSize); - var status = input.Status?.Trim(); - var taskType = input.TaskType?.Trim(); - var sourceRefId = input.SourceRefId?.Trim(); - - Expression> predicate = x => - (!customerId.HasValue || x.CustomerId == customerId.Value) && - (string.IsNullOrWhiteSpace(status) || x.Status == status) && - (string.IsNullOrWhiteSpace(taskType) || x.TaskType == taskType) && - (string.IsNullOrWhiteSpace(sourceRefId) || x.SourceRefId == sourceRefId); - - var items = await TaskRepository.GetPagedListAsync( - predicate, + var (items, total) = await TaskRepository.GetFilteredPagedListAsync( pageIndex, pageSize, - new Dictionary { [nameof(XhsTask.Id)] = false }); - var total = await TaskRepository.CountAsync(predicate); + customerId, + input.Status, + input.TaskType, + input.SourceRefId); return new OmsPageResult { diff --git a/src/Oms.Domain/XhsAggregate/Repositories/IXhsRepositories.cs b/src/Oms.Domain/XhsAggregate/Repositories/IXhsRepositories.cs index 62a69cd..c37a36e 100644 --- a/src/Oms.Domain/XhsAggregate/Repositories/IXhsRepositories.cs +++ b/src/Oms.Domain/XhsAggregate/Repositories/IXhsRepositories.cs @@ -15,11 +15,28 @@ public interface IXhsTaskRepository : IRepository int agentId, int limit, CancellationToken cancellationToken = default); + + Task<(List Items, int Total)> GetFilteredPagedListAsync( + int pageIndex, + int pageSize, + int? customerId, + string? status, + string? taskType, + string? sourceRefId, + CancellationToken cancellationToken = default); } public interface IXhsPublishRecordRepository : IRepository { Task> GetDueScheduledAsync(int limit, CancellationToken cancellationToken = default); + + Task<(List Items, int Total)> GetFilteredPagedListAsync( + int pageIndex, + int pageSize, + int? customerId, + string? status, + string? keyword, + CancellationToken cancellationToken = default); } public interface IXhsAnalysisSessionRepository : IRepository; diff --git a/src/Oms.SqlSugarCore/Xhs/XhsRepositories.cs b/src/Oms.SqlSugarCore/Xhs/XhsRepositories.cs index f1c8ef6..ab355c9 100644 --- a/src/Oms.SqlSugarCore/Xhs/XhsRepositories.cs +++ b/src/Oms.SqlSugarCore/Xhs/XhsRepositories.cs @@ -15,6 +15,49 @@ public class XhsAgentRepository : OmsSqlSugarRepository, IXhsAgentRepo public class XhsTaskRepository : OmsSqlSugarRepository, IXhsTaskRepository, IScopedDependency { + public async Task<(List Items, int Total)> GetFilteredPagedListAsync( + int pageIndex, + int pageSize, + int? customerId, + string? status, + string? taskType, + string? sourceRefId, + CancellationToken cancellationToken = default) + { + var db = await GetDbContextAsync(cancellationToken); + var query = db.Queryable(); + + if (customerId.HasValue) + { + var cid = customerId.Value; + query = query.Where(x => x.CustomerId == cid); + } + + status = status?.Trim(); + if (!string.IsNullOrWhiteSpace(status)) + { + query = query.Where(x => x.Status == status); + } + + taskType = taskType?.Trim(); + if (!string.IsNullOrWhiteSpace(taskType)) + { + query = query.Where(x => x.TaskType == taskType); + } + + sourceRefId = sourceRefId?.Trim(); + if (!string.IsNullOrWhiteSpace(sourceRefId)) + { + query = query.Where(x => x.SourceRefId == sourceRefId); + } + + var total = await query.CountAsync(cancellationToken); + var items = await query + .OrderBy(x => x.Id, OrderByType.Desc) + .ToPageListAsync(pageIndex, pageSize, cancellationToken); + return (items, total); + } + public async Task> ClaimPendingAsync( int customerId, int agentId, @@ -54,6 +97,42 @@ public class XhsTaskRepository : OmsSqlSugarRepository, IXhsTaskReposit public class XhsPublishRecordRepository : OmsSqlSugarRepository, IXhsPublishRecordRepository, IScopedDependency { + public async Task<(List Items, int Total)> GetFilteredPagedListAsync( + int pageIndex, + int pageSize, + int? customerId, + string? status, + string? keyword, + CancellationToken cancellationToken = default) + { + var db = await GetDbContextAsync(cancellationToken); + var query = db.Queryable(); + + if (customerId.HasValue) + { + var cid = customerId.Value; + query = query.Where(x => x.CustomerId == cid); + } + + status = status?.Trim(); + if (!string.IsNullOrWhiteSpace(status)) + { + query = query.Where(x => x.Status == status); + } + + keyword = keyword?.Trim(); + if (!string.IsNullOrWhiteSpace(keyword)) + { + query = query.Where(x => x.Title.Contains(keyword)); + } + + var total = await query.CountAsync(cancellationToken); + var items = await query + .OrderBy(x => x.CreateTime, OrderByType.Desc) + .ToPageListAsync(pageIndex, pageSize, cancellationToken); + return (items, total); + } + public async Task> GetDueScheduledAsync(int limit, CancellationToken cancellationToken = default) { var now = DateTime.Now; diff --git a/src/SimpleAdmin.Api/Auth/AuthService.cs b/src/SimpleAdmin.Api/Auth/AuthService.cs index 1c67557..0510082 100644 --- a/src/SimpleAdmin.Api/Auth/AuthService.cs +++ b/src/SimpleAdmin.Api/Auth/AuthService.cs @@ -32,7 +32,6 @@ namespace SimpleAdmin.Api.Auth; public partial class AuthService( AccountManager accountManager, UserService userService, - MenuService menuService, IJwtService jwtService, IUserRepository userRepository, IWebHostEnvironment webHostEnvironment, @@ -353,6 +352,7 @@ public partial class AuthService( private async Task BuildAdminUserInfoAsync(Guid userId, CancellationToken cancellationToken) { var userInfo = await userService.GetAsync(userId, cancellationToken); + var permissions = await userService.GetUserPermissionsAsync(userId, cancellationToken); return new UserInfoDto { Id = userInfo.Id, @@ -363,8 +363,8 @@ public partial class AuthService( OrganizationId = userInfo.OrganizationId, OrganizationName = userInfo.OrganizationName, Roles = userInfo.Roles, - Permissions = await userService.GetUserPermissionsAsync(userId, cancellationToken), - Menus = await menuService.GetUserMenusAsync(userId, cancellationToken), + Permissions = permissions, + Menus = await BuildMenusByPermissionsAsync(permissions, cancellationToken), CreatedAt = userInfo.CreatedAt, CreatedBy = userInfo.CreatedBy, UpdatedAt = userInfo.UpdatedAt,