fix: 公告广播可见性改为反序列化判定,避免 JSON 列比较失败
All checks were successful
Build and Deploy / deploy (push) Successful in 1m1s
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:
parent
c6e765e6e2
commit
7a0affe2d4
2
db.sql
2
db.sql
@ -1220,7 +1220,7 @@ CREATE TABLE `t_notice` (
|
|||||||
`Content` TEXT NOT NULL COMMENT '通知内容',
|
`Content` TEXT NOT NULL COMMENT '通知内容',
|
||||||
`IsPopup` TINYINT NOT NULL DEFAULT 0 COMMENT '是否弹窗:0-否,1-是',
|
`IsPopup` TINYINT NOT NULL DEFAULT 0 COMMENT '是否弹窗:0-否,1-是',
|
||||||
`ReceiverType` VARCHAR(16) NOT NULL COMMENT '接收对象:user/customer',
|
`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-是',
|
`IsDeleted` TINYINT NOT NULL DEFAULT 0 COMMENT '软删除:0-否,1-是',
|
||||||
`ConcurrencyStamp` CHAR(36) NOT NULL DEFAULT (UUID()) COMMENT '并发戳',
|
`ConcurrencyStamp` CHAR(36) NOT NULL DEFAULT (UUID()) COMMENT '并发戳',
|
||||||
`CreateUserId` INT NOT NULL DEFAULT 0 COMMENT '创建人',
|
`CreateUserId` INT NOT NULL DEFAULT 0 COMMENT '创建人',
|
||||||
|
|||||||
25
sql/migrate_notice_receiver_ids_text.sql
Normal file
25
sql/migrate_notice_receiver_ids_text.sql
Normal 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;
|
||||||
@ -65,22 +65,25 @@ public class NoticeService : ApplicationService
|
|||||||
.Select(x => x.NoticeId)
|
.Select(x => x.NoticeId)
|
||||||
.Distinct()
|
.Distinct()
|
||||||
.ToList();
|
.ToList();
|
||||||
|
var myReadIdSet = myReadNoticeIds.ToHashSet();
|
||||||
|
|
||||||
var predicate = manageAll
|
// 先按类型/关键词查,再在内存按 ReceiverIds 判可见性。
|
||||||
? BuildManageListPredicate(input)
|
// MySQL JSON 列与字符串 "[]" 比较会失败,导致广播对客户/收件箱不可见。
|
||||||
: BuildListPredicate(
|
var basePredicate = BuildManageListPredicate(input);
|
||||||
input,
|
var candidates = await NoticeRepository.GetListAsync(basePredicate);
|
||||||
receiverUserId,
|
var visible = candidates
|
||||||
expectedReceiverType,
|
.Where(x => manageAll || IsVisibleToReceiver(x, receiverUserId, expectedReceiverType))
|
||||||
input.OnlyUnread == true ? myReadNoticeIds : null);
|
.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 pageIndex = OmsDocHelper.NormalizePage(input.Page);
|
||||||
var pageSize = OmsDocHelper.NormalizePageSize(input.PageSize);
|
var pageSize = OmsDocHelper.NormalizePageSize(input.PageSize);
|
||||||
var notices = await NoticeRepository.GetPagedListAsync(
|
var total = visible.Count;
|
||||||
predicate,
|
var notices = visible
|
||||||
pageIndex,
|
.Skip(Math.Max(pageIndex - 1, 0) * pageSize)
|
||||||
pageSize,
|
.Take(pageSize)
|
||||||
new Dictionary<string, bool> { [nameof(Notice.CreateTime)] = false, [nameof(Notice.Id)] = false });
|
.ToList();
|
||||||
var total = await NoticeRepository.CountAsync(predicate);
|
|
||||||
|
|
||||||
var noticeIds = notices.Select(x => x.Id).Distinct().ToArray();
|
var noticeIds = notices.Select(x => x.Id).Distinct().ToArray();
|
||||||
var reads = noticeIds.Length == 0
|
var reads = noticeIds.Length == 0
|
||||||
@ -91,8 +94,6 @@ public class NoticeService : ApplicationService
|
|||||||
.ToDictionary(
|
.ToDictionary(
|
||||||
x => x.Key,
|
x => x.Key,
|
||||||
x => (int)x.Select(item => $"{item.ReceiverType}:{item.ReceiverId}").Distinct(StringComparer.Ordinal).Count());
|
x => (int)x.Select(item => $"{item.ReceiverType}:{item.ReceiverId}").Distinct(StringComparer.Ordinal).Count());
|
||||||
var myReadIdSet = myReadNoticeIds.ToHashSet();
|
|
||||||
|
|
||||||
var selectionIdMap = await BuildReceiverSelectionIdMapAsync(notices);
|
var selectionIdMap = await BuildReceiverSelectionIdMapAsync(notices);
|
||||||
|
|
||||||
return new OmsPageResult<NoticeOutput>
|
return new OmsPageResult<NoticeOutput>
|
||||||
@ -365,33 +366,19 @@ public class NoticeService : ApplicationService
|
|||||||
x.Content.Contains(keyword));
|
x.Content.Contains(keyword));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Expression<Func<Notice, bool>> BuildListPredicate(
|
/// <summary>
|
||||||
NoticeListInput input,
|
/// 空接收人 = 全体广播(员工/客户均可见);否则须类型匹配且 mapId 在名单内。
|
||||||
int receiverUserId,
|
/// </summary>
|
||||||
string expectedReceiverType,
|
private static bool IsVisibleToReceiver(Notice notice, int receiverUserId, string expectedReceiverType)
|
||||||
IReadOnlyCollection<int>? excludeNoticeIds)
|
|
||||||
{
|
{
|
||||||
var noticeType = input.NoticeType?.Trim();
|
var receiverIds = OmsDocHelper.DeserializeJson<List<int>>(notice.ReceiverIds) ?? [];
|
||||||
var receiverType = input.ReceiverType?.Trim();
|
if (receiverIds.Count == 0)
|
||||||
var keyword = input.Keyword?.Trim();
|
{
|
||||||
var receiverIdToken = receiverUserId.ToString();
|
return true;
|
||||||
var excluded = excludeNoticeIds?.Where(x => x > 0).Distinct().ToList() ?? [];
|
}
|
||||||
return x =>
|
|
||||||
(string.IsNullOrWhiteSpace(noticeType) || x.NoticeType == noticeType) &&
|
return string.Equals(notice.ReceiverType, expectedReceiverType, StringComparison.OrdinalIgnoreCase)
|
||||||
(string.IsNullOrWhiteSpace(receiverType) || x.ReceiverType == receiverType) &&
|
&& receiverIds.Contains(receiverUserId);
|
||||||
(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));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<Notice> GetNoticeOrThrowAsync(int id)
|
private async Task<Notice> GetNoticeOrThrowAsync(int id)
|
||||||
|
|||||||
@ -16,5 +16,9 @@ public class Notice : OmsAuditedAggregateRoot
|
|||||||
|
|
||||||
public string ReceiverType { get; set; } = string.Empty;
|
public string ReceiverType { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 接收人 mapId 列表 JSON 数组字符串,如 [] / [1,2]。用 text 存储,避免 MySQL JSON 列与字符串比较失败导致广播不可见。
|
||||||
|
/// </summary>
|
||||||
|
[SugarColumn(ColumnDataType = "text")]
|
||||||
public string ReceiverIds { get; set; } = "[]";
|
public string ReceiverIds { get; set; } = "[]";
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user