fix: 公告广播可见性改为反序列化判定,避免 JSON 列比较失败
All checks were successful
Build and Deploy / deploy (push) Successful in 1m1s

ReceiverIds 改为 text 存储并补充迁移;广播对客户/收件箱可见。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
zzh 2026-10-16 09:57:25 +08:00
parent c6e765e6e2
commit 7a0affe2d4
4 changed files with 58 additions and 42 deletions

2
db.sql
View File

@ -1220,7 +1220,7 @@ CREATE TABLE `t_notice` (
`Content` TEXT NOT NULL COMMENT '通知内容',
`IsPopup` TINYINT NOT NULL DEFAULT 0 COMMENT '是否弹窗:0-否,1-是',
`ReceiverType` VARCHAR(16) NOT NULL COMMENT '接收对象:user/customer',
`ReceiverIds` JSON NOT NULL COMMENT '接收对象ID列表(JSON数组)',
`ReceiverIds` TEXT NOT NULL COMMENT '接收对象ID列表(JSON数组字符串)',
`IsDeleted` TINYINT NOT NULL DEFAULT 0 COMMENT '软删除:0-否,1-是',
`ConcurrencyStamp` CHAR(36) NOT NULL DEFAULT (UUID()) COMMENT '并发戳',
`CreateUserId` INT NOT NULL DEFAULT 0 COMMENT '创建人',

View File

@ -0,0 +1,25 @@
-- =============================================================================
-- migrate_notice_receiver_ids_text.sql
-- t_notice.ReceiverIds:JSON → TEXT,保证广播([])与定向名单可用字符串条件查询
-- 可重复执行
-- =============================================================================
SET @db := DATABASE();
SET @col_type := (
SELECT DATA_TYPE
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = @db
AND TABLE_NAME = 't_notice'
AND COLUMN_NAME = 'ReceiverIds'
LIMIT 1
);
SET @sql := IF(
@col_type IS NOT NULL AND LOWER(IFNULL(@col_type, '')) = 'json',
'ALTER TABLE `t_notice`
MODIFY COLUMN `ReceiverIds` TEXT NOT NULL COMMENT ''接收对象ID列表(JSON数组字符串)''',
'SELECT ''t_notice.ReceiverIds already text or missing'' AS info'
);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;

View File

@ -65,22 +65,25 @@ public class NoticeService : ApplicationService
.Select(x => x.NoticeId)
.Distinct()
.ToList();
var myReadIdSet = myReadNoticeIds.ToHashSet();
var predicate = manageAll
? BuildManageListPredicate(input)
: BuildListPredicate(
input,
receiverUserId,
expectedReceiverType,
input.OnlyUnread == true ? myReadNoticeIds : null);
// 先按类型/关键词查,再在内存按 ReceiverIds 判可见性。
// MySQL JSON 列与字符串 "[]" 比较会失败,导致广播对客户/收件箱不可见。
var basePredicate = BuildManageListPredicate(input);
var candidates = await NoticeRepository.GetListAsync(basePredicate);
var visible = candidates
.Where(x => manageAll || IsVisibleToReceiver(x, receiverUserId, expectedReceiverType))
.Where(x => input.OnlyUnread != true || !myReadIdSet.Contains(x.Id))
.OrderByDescending(x => x.CreateTime)
.ThenByDescending(x => x.Id)
.ToList();
var pageIndex = OmsDocHelper.NormalizePage(input.Page);
var pageSize = OmsDocHelper.NormalizePageSize(input.PageSize);
var notices = await NoticeRepository.GetPagedListAsync(
predicate,
pageIndex,
pageSize,
new Dictionary<string, bool> { [nameof(Notice.CreateTime)] = false, [nameof(Notice.Id)] = false });
var total = await NoticeRepository.CountAsync(predicate);
var total = visible.Count;
var notices = visible
.Skip(Math.Max(pageIndex - 1, 0) * pageSize)
.Take(pageSize)
.ToList();
var noticeIds = notices.Select(x => x.Id).Distinct().ToArray();
var reads = noticeIds.Length == 0
@ -91,8 +94,6 @@ public class NoticeService : ApplicationService
.ToDictionary(
x => x.Key,
x => (int)x.Select(item => $"{item.ReceiverType}:{item.ReceiverId}").Distinct(StringComparer.Ordinal).Count());
var myReadIdSet = myReadNoticeIds.ToHashSet();
var selectionIdMap = await BuildReceiverSelectionIdMapAsync(notices);
return new OmsPageResult<NoticeOutput>
@ -365,33 +366,19 @@ public class NoticeService : ApplicationService
x.Content.Contains(keyword));
}
private static Expression<Func<Notice, bool>> BuildListPredicate(
NoticeListInput input,
int receiverUserId,
string expectedReceiverType,
IReadOnlyCollection<int>? excludeNoticeIds)
/// <summary>
/// 空接收人 = 全体广播(员工/客户均可见);否则须类型匹配且 mapId 在名单内。
/// </summary>
private static bool IsVisibleToReceiver(Notice notice, int receiverUserId, string expectedReceiverType)
{
var noticeType = input.NoticeType?.Trim();
var receiverType = input.ReceiverType?.Trim();
var keyword = input.Keyword?.Trim();
var receiverIdToken = receiverUserId.ToString();
var excluded = excludeNoticeIds?.Where(x => x > 0).Distinct().ToList() ?? [];
return x =>
(string.IsNullOrWhiteSpace(noticeType) || x.NoticeType == noticeType) &&
(string.IsNullOrWhiteSpace(receiverType) || x.ReceiverType == receiverType) &&
(string.IsNullOrWhiteSpace(keyword) ||
x.Title.Contains(keyword) ||
x.Content.Contains(keyword)) &&
(
// 空接收人列表 = 全体广播(员工/客户均可见)
x.ReceiverIds == "[]" ||
(x.ReceiverType == expectedReceiverType &&
(x.ReceiverIds == $"[{receiverIdToken}]" ||
x.ReceiverIds.StartsWith($"[{receiverIdToken},") ||
x.ReceiverIds.Contains($",{receiverIdToken},") ||
x.ReceiverIds.EndsWith($",{receiverIdToken}]")))
) &&
(excluded.Count == 0 || !excluded.Contains(x.Id));
var receiverIds = OmsDocHelper.DeserializeJson<List<int>>(notice.ReceiverIds) ?? [];
if (receiverIds.Count == 0)
{
return true;
}
return string.Equals(notice.ReceiverType, expectedReceiverType, StringComparison.OrdinalIgnoreCase)
&& receiverIds.Contains(receiverUserId);
}
private async Task<Notice> GetNoticeOrThrowAsync(int id)

View File

@ -16,5 +16,9 @@ public class Notice : OmsAuditedAggregateRoot
public string ReceiverType { get; set; } = string.Empty;
/// <summary>
/// 接收人 mapId 列表 JSON 数组字符串,如 [] / [1,2]。用 text 存储,避免 MySQL JSON 列与字符串比较失败导致广播不可见。
/// </summary>
[SugarColumn(ColumnDataType = "text")]
public string ReceiverIds { get; set; } = "[]";
}