24 KiB
消息业务逻辑文档
一、消息系统概述
消息系统用于处理用户之间的互动消息和系统通知,主要包括四种消息类型:
- MessageType = 1: 评论回复消息
- MessageType = 2: 点赞消息
- MessageType = 3: 关注消息
- MessageType = 4: 系统通知
二、数据模型
2.1 T_Messages(消息表)
| 字段 | 类型 | 说明 |
|---|---|---|
| Id | long | 消息ID,主键自增 |
| ReceiverId | long | 接收消息的用户ID |
| SenderId | long? | 发送消息的用户ID(系统消息时为NULL) |
| MessageType | int | 消息类型:1-评论回复,2-点赞,3-关注,4-系统通知 |
| MessageContent | string | 消息内容文本(最大500字符) |
| ContentType | int | 内容类型:1-帖子,2-评论 |
| ContentId | long? | 关联的内容ID(帖子ID或评论ID) |
| IsRead | bool | 是否已读,默认false |
| ReadAt | DateTime? | 消息阅读时间 |
| CreatedAt | DateTime | 消息创建时间 |
2.2 T_SystemNotifications(系统通知表)
| 字段 | 类型 | 说明 |
|---|---|---|
| Id | long | 通知ID,主键自增 |
| Title | string | 通知标题(最大100字符) |
| Content | string | 通知正文内容 |
| IsActive | bool | 是否启用该通知,默认true |
| PublishTime | DateTime? | 通知发布时间 |
| ExpireTime | DateTime? | 通知过期时间 |
| CreatedAt | DateTime | 通知创建时间 |
| UpdatedAt | DateTime | 通知更新时间 |
三、核心业务逻辑
3.1 GetMessages - 获取消息列表(通用方法)
功能描述:获取当前用户的消息列表,支持按消息类型和已读状态过滤。
业务逻辑流程:
-
基础查询构建
- 查询条件:
ReceiverId == currentUserId - 根据请求参数过滤消息类型(
MessageType) - 根据请求参数过滤已读状态(
IsRead)
- 查询条件:
-
系统通知特殊处理(当
MessageType == 4时)- 查询未创建消息记录的系统通知:
SELECT n.* FROM T_SystemNotifications n LEFT JOIN T_Messages m ON n.Id = m.SenderId AND m.MessageType = 4 AND m.ReceiverId = currentUserId WHERE m.Id IS NULL AND (n.ExpireTime > NOW() OR n.ExpireTime IS NULL) AND n.PublishTime <= NOW() - 如果存在未创建消息的系统通知,自动为它们创建消息记录:
ReceiverId= 当前用户IDSenderId= 通知ID(使用通知ID作为SenderId)MessageType= 4MessageContent="{通知标题}: {通知内容}"IsRead= falseCreatedAt= 当前时间
- 查询未创建消息记录的系统通知:
-
排序与分页
- 按创建时间倒序排列(
OrderByDescending(x => x.CreatedAt)) - 支持分页查询(
PageIndex,PageSize)
- 按创建时间倒序排列(
-
关联数据查询
- 用户信息:查询发送者信息(排除系统通知类型)
- 从
T_Users表获取发送者详细信息(昵称、头像、VIP状态、认证状态等)
- 从
- 帖子信息:当
ContentType == 1时,查询关联的帖子信息 - 评论信息:当
ContentType == 2时,查询关联的评论信息- 如果评论关联了帖子,同时获取帖子信息
- 用户信息:查询发送者信息(排除系统通知类型)
-
数据组装
- 构建
MessageListItemDto对象,包含:- 消息基本信息(ID、类型、内容、创建时间、已读状态)
- 发送者信息(
MessageSenderDto,系统通知时为空对象) - 关联内容信息(
MessageRelatedContentDto,包含帖子标题、评论内容等)
- 构建
-
返回结果
- 总记录数(
Total) - 总页数(
TotalPages) - 未读消息数量(
UnreadCount) - 消息列表(
Items)
- 总记录数(
3.2 GetSystemNotifications - 获取系统通知列表
功能描述:获取系统通知列表(从 T_SystemNotifications 表查询,非消息表)。
业务逻辑流程:
-
查询条件
IsActive == trueExpireTime == null OR ExpireTime > 当前时间PublishTime == null OR PublishTime <= 当前时间
-
排序
- 按发布时间倒序(
OrderByDescending(x => x.PublishTime ?? x.CreatedAt))
- 按发布时间倒序(
-
分页查询
- 支持分页参数
-
返回结果
- 系统通知列表(不包含未读数量统计)
3.3 ReadMessage - 标记单条消息已读
功能描述:将指定消息标记为已读。
业务逻辑流程:
-
权限验证
- 验证消息是否存在
- 验证消息是否属于当前用户(
ReceiverId == currentUserId)
-
更新消息状态
IsRead = trueReadAt = 当前时间
-
返回结果
- 消息ID、已读状态、阅读时间
3.4 ReadBatchMessages - 批量标记消息已读
功能描述:批量将多条消息标记为已读。
业务逻辑流程:
-
参数验证
- 消息ID列表不能为空
- 批量操作数量限制:最多100条
-
批量更新
- 使用批量更新操作,更新条件:
ReceiverId == currentUserIdId IN (消息ID列表)
- 设置
IsRead = true,ReadAt = 当前时间
- 使用批量更新操作,更新条件:
-
返回结果
- 成功更新的数量(
SuccessCount) - 阅读时间(
ReadAt)
- 成功更新的数量(
3.5 ReadAllMessages - 标记所有消息已读
功能描述:将当前用户的所有未读消息(或指定类型的未读消息)标记为已读。
业务逻辑流程:
-
查询未读消息
- 基础条件:
ReceiverId == currentUserId AND IsRead == false - 如果指定了
MessageType,则按类型过滤
- 基础条件:
-
批量更新
- 遍历所有未读消息,设置
IsRead = true,ReadAt = 当前时间 - 批量更新到数据库
- 遍历所有未读消息,设置
-
返回结果
- 成功更新的数量(
SuccessCount) - 阅读时间(
ReadAt)
- 成功更新的数量(
3.6 DeleteMessage - 删除消息
功能描述:删除指定消息。
业务逻辑流程:
-
权限验证
- 验证消息是否存在
- 验证消息是否属于当前用户(
ReceiverId == currentUserId)
-
删除操作
- 物理删除消息记录
-
返回结果
- 操作成功标识
3.7 GetUnreadCount - 获取未读消息数量
功能描述:获取当前用户各类消息的未读数量统计。
业务逻辑流程:
-
查询未读消息
- 查询条件:
ReceiverId == currentUserId AND IsRead == false - 按消息类型分组统计
- 查询条件:
-
统计结果
TotalUnread:总未读数量CommentReplyUnread:评论回复未读数量(MessageType == 1)LikeUnread:点赞未读数量(MessageType == 2)FollowUnread:关注未读数量(MessageType == 3)SystemUnread:系统通知未读数量(MessageType == 4)
3.8 GetCommentRepliesMessages - 获取评论回复消息
功能描述:专门获取评论回复类型的消息列表。
业务逻辑流程:
-
查询构建
- 固定条件:
ReceiverId == currentUserId AND MessageType == 1 - 可选过滤:按已读状态过滤
- 固定条件:
-
关联数据查询
- 发送者信息:查询回复评论的用户信息
- 评论信息:查询被回复的评论内容
- 帖子信息:查询评论所属的帖子信息
- 我的评论信息:查询当前用户被回复的原始评论(通过
ParentCommentId关联)
-
数据组装
- 构建
CommentReplyMessageDto对象,包含:- 消息基本信息
- 发送者信息
- 回复的评论信息(
CommentInfoDto) - 我的原始评论信息(
MyCommentInfoDto)
- 构建
3.9 GetLikesMessages - 获取点赞消息
功能描述:专门获取点赞类型的消息列表。
业务逻辑流程:
-
查询构建
- 固定条件:
ReceiverId == currentUserId AND MessageType == 2 - 可选过滤:按已读状态过滤
- 固定条件:
-
关联数据查询
- 发送者信息:查询点赞的用户信息
- 帖子信息:当
ContentType == 1时,查询被点赞的帖子信息 - 评论信息:当
ContentType == 2时,查询被点赞的评论信息- 如果评论关联了帖子,同时获取帖子信息
-
数据组装
- 构建
LikeMessageDto对象,包含:- 消息基本信息
- 发送者信息
- 被点赞的内容信息(
LikedContentDto,包含内容类型、ID、帖子标题、评论内容等)
- 构建
3.10 GetFollowsMessages - 获取关注消息
功能描述:专门获取关注类型的消息列表。
业务逻辑流程:
-
查询构建
- 固定条件:
ReceiverId == currentUserId AND MessageType == 3 - 可选过滤:按已读状态过滤
- 固定条件:
-
关联数据查询
- 关注者信息:查询关注当前用户的用户信息
-
数据组装
- 构建
FollowMessageDto对象,包含:- 消息基本信息
- 关注者信息(
FollowerInfoDto,包含用户ID、昵称、头像、VIP状态、认证状态、个性签名等)
- 构建
四、系统通知的特殊机制
4.1 系统通知的双表设计
系统通知采用双表设计:
- T_SystemNotifications:存储系统通知的原始数据(标题、内容、发布时间、过期时间等)
- T_Messages:存储用户级别的消息记录(用于已读/未读状态管理)
4.2 系统通知的自动创建机制
当用户查询系统通知类型的消息时(MessageType == 4),系统会:
- 检查是否存在未创建消息记录的系统通知
- 查询条件:
- 系统通知已发布(
PublishTime <= 当前时间) - 系统通知未过期(
ExpireTime > 当前时间 OR ExpireTime IS NULL) - 该用户尚未收到该通知的消息记录(通过 LEFT JOIN 判断)
- 系统通知已发布(
- 自动为符合条件的系统通知创建消息记录
- 使用通知ID作为消息的
SenderId,便于关联
4.3 系统通知的查询方式
系统提供两种查询系统通知的方式:
- GetSystemNotifications:从
T_SystemNotifications表查询,返回所有系统通知(不区分用户) - GetMessages(MessageType=4):从
T_Messages表查询,返回当前用户收到的系统通知(包含已读/未读状态)
五、消息类型与内容类型的关系
5.1 消息类型(MessageType)
| 类型值 | 类型名称 | 说明 | SenderId | ContentId |
|---|---|---|---|---|
| 1 | 评论回复 | 用户回复了评论 | 回复者用户ID | 评论ID |
| 2 | 点赞 | 用户点赞了内容 | 点赞者用户ID | 帖子ID或评论ID |
| 3 | 关注 | 用户关注了当前用户 | 关注者用户ID | 无 |
| 4 | 系统通知 | 系统发送的通知 | 通知ID(或NULL) | 可选 |
5.2 内容类型(ContentType)
| 类型值 | 类型名称 | 说明 | 使用场景 |
|---|---|---|---|
| 1 | 帖子 | 关联的是帖子 | 点赞帖子、系统通知关联帖子 |
| 2 | 评论 | 关联的是评论 | 评论回复、点赞评论 |
5.3 消息类型与内容类型的组合
- 评论回复(MessageType=1):
ContentType=2,ContentId=评论ID - 点赞(MessageType=2):
ContentType=1或2,ContentId=帖子ID或评论ID - 关注(MessageType=3):
ContentType=0,ContentId=null - 系统通知(MessageType=4):
ContentType=0或1或2,ContentId=可选
六、数据查询优化
6.1 批量查询优化
在获取消息列表时,采用批量查询优化:
- 先查询消息记录
- 收集所有需要关联的ID(发送者ID、帖子ID、评论ID)
- 批量查询关联数据
- 使用字典(Dictionary)存储,提高查找效率
- 最后组装返回数据
6.2 分页查询
所有列表查询方法都支持分页:
PageIndex:页码(从1开始)PageSize:每页数量- 返回
Total(总记录数)和TotalPages(总页数)
七、注意事项
-
系统通知的特殊性
- 系统通知的
SenderId可能为通知ID,也可能为NULL - 查询系统通知时,不会查询发送者信息(用户信息)
- 系统通知的
-
已读状态管理
- 所有消息默认
IsRead = false - 标记已读时会同时更新
ReadAt时间戳
- 所有消息默认
-
权限控制
- 所有消息操作都验证
ReceiverId == currentUserId - 确保用户只能操作自己的消息
- 所有消息操作都验证
-
系统通知的时效性
- 系统通知有发布时间(
PublishTime)和过期时间(ExpireTime) - 只有已发布且未过期的通知才会被自动创建消息记录
- 系统通知有发布时间(
-
批量操作限制
- 批量标记已读操作限制最多100条消息
八、待改造点
根据当前业务逻辑,以下方面可能需要改造:
- 系统消息扩展
- 可向指定用户/类型用户发送站内信,如节假日向认证用户发送。用户点击按钮,在弹窗中填写收货地址并提交,视为参与该活动此类活动有结束时间,到期后按钮处于灰色不可点击状态。弹窗只展示联系方式和收获地址。(用户表中有CertifiedType认证类型)
- 这个需求我们需要讨论一下,要怎么修改表结构。先把前面两个需求做了。
九、系统消息扩展需求分析
9.1 需求概述
系统需要支持活动类型的系统消息,具备以下特性:
- 定向发送:可向指定用户或按用户类型(如认证类型)批量发送
- 活动参与:用户可通过点击按钮参与活动,填写收货地址等信息
- 时效控制:活动有结束时间,到期后用户无法参与
- 数据展示:后台可查看用户的参与信息(联系方式、收货地址)
9.2 业务场景示例
场景:节假日向认证用户发送活动通知
- 系统创建活动通知,设置目标用户类型为"认证用户"(CertifiedType = 1 或 2)
- 符合条件的用户收到系统消息
- 用户点击"参与活动"按钮,弹出表单
- 用户填写收货地址、联系方式等信息并提交
- 活动结束后,按钮变为灰色不可点击状态
- 后台管理员可查看所有参与用户的收货信息
9.3 表结构设计分析
9.3.1 T_SystemNotifications 表扩展
需要在现有系统通知表基础上新增字段:
| 字段名 | 类型 | 说明 | 是否必填 |
|---|---|---|---|
| NotificationType | int | 通知类型:1-普通通知,2-活动通知 | 是 |
| TargetUserType | int? | 目标用户类型:null-所有用户,1-指定用户列表,2-按认证类型筛选 | 否 |
| TargetCertifiedType | int? | 目标认证类型:1-SK认证,2-其他认证(当TargetUserType=2时使用) | 否 |
| TargetUserIds | string | 目标用户ID列表(JSON格式,当TargetUserType=1时使用) | 否 |
| ActivityEndTime | DateTime? | 活动结束时间(当NotificationType=2时使用) | 否 |
| IsActivity | bool | 是否为活动类型通知(是否需要用户参与) | 否,默认false |
| ActivityButtonText | string | 活动按钮文本(如"参与活动"、"立即领取"等) | 否 |
说明:
NotificationType:区分普通通知和活动通知TargetUserType:支持三种发送方式- null 或 0:所有用户
- 1:指定用户列表(通过TargetUserIds存储)
- 2:按用户类型筛选(通过TargetCertifiedType指定)
IsActivity:标识是否为活动类型,决定是否显示参与按钮ActivityEndTime:活动结束时间,用于前端判断按钮是否可点击
9.3.2 新建表:T_SystemNotificationParticipants(系统通知参与记录表)
用于存储用户参与活动的信息:
| 字段名 | 类型 | 说明 |
|---|---|---|
| Id | long | 主键,自增 |
| NotificationId | long | 系统通知ID,外键关联T_SystemNotifications |
| UserId | long | 用户ID,外键关联T_Users |
| ContactInfo | string | 联系方式(电话/微信等) |
| ShippingAddress | string | 收货地址 |
| ParticipantName | string | 收货人姓名(可选) |
| ParticipantPhone | string | 收货人电话(可选) |
| Status | int | 参与状态:1-已提交,2-已处理,3-已发货等 |
| SubmittedAt | DateTime | 提交时间 |
| CreatedAt | DateTime | 创建时间 |
| UpdatedAt | DateTime | 更新时间 |
说明:
- 一个用户对一个活动只能参与一次(通过 NotificationId + UserId 唯一约束)
- 支持扩展参与状态,便于后续流程管理
- 联系方式、收货地址等字段长度需要根据实际需求设定
9.4 业务逻辑流程
9.4.1 创建活动通知流程
-
管理员创建活动通知
- 填写通知标题、内容
- 选择通知类型为"活动通知"(NotificationType = 2)
- 设置
IsActivity = true - 选择发送范围:
- 所有用户:TargetUserType = null
- 指定用户:TargetUserType = 1,填写 TargetUserIds(JSON数组)
- 按类型筛选:TargetUserType = 2,选择 TargetCertifiedType
- 设置活动结束时间(ActivityEndTime)
- 设置活动按钮文本(ActivityButtonText)
- 设置发布时间(PublishTime)
-
系统发送通知
- 根据 TargetUserType 和 TargetCertifiedType 筛选目标用户
- 为每个目标用户创建 T_Messages 记录(MessageType = 4)
- 使用通知ID作为 SenderId
9.4.2 用户查看活动通知流程
-
用户查看消息列表
- 调用
GetMessages(MessageType=4)获取系统通知 - 系统自动创建未创建的系统通知消息记录
- 调用
-
前端展示判断
- 检查通知的
IsActivity字段 - 如果为 true,显示活动按钮
- 检查
ActivityEndTime是否已过期 - 如果已过期,按钮置灰不可点击
- 如果未过期,按钮可点击
- 检查通知的
-
检查用户是否已参与
- 前端调用接口检查当前用户是否已参与该活动
- 如果已参与,按钮显示"已参与"或隐藏按钮
- 如果未参与且未过期,显示参与按钮
9.4.3 用户参与活动流程
-
用户点击参与按钮
- 前端弹出表单(弹窗)
- 表单字段:
- 联系方式(ContactInfo)
- 收货地址(ShippingAddress)
- 提交按钮
-
用户提交表单
- 前端调用参与活动接口
- 后端验证:
- 活动是否存在且有效
- 活动是否已过期(ActivityEndTime)
- 用户是否已参与过(防止重复提交)
- 必填字段是否完整
-
保存参与记录
- 在 T_SystemNotificationParticipants 表中创建记录
- Status = 1(已提交)
- 记录提交时间
-
返回结果
- 成功:返回参与成功信息
- 失败:返回具体错误原因
9.5 接口设计思路
9.5.1 创建活动通知接口(后台管理)暂不需要,后台有后台的项目,不在这个项目中处理
接口:POST /api/SystemNotifications/CreateActivityNotification
请求参数:
public class CreateActivityNotificationReq
{
public string Title { get; set; } // 通知标题
public string Content { get; set; } // 通知内容
public int TargetUserType { get; set; } // 目标用户类型:0-所有,1-指定用户,2-按认证类型
public int? TargetCertifiedType { get; set; } // 目标认证类型(当TargetUserType=2时)
public List<long>? TargetUserIds { get; set; } // 目标用户ID列表(当TargetUserType=1时)
public DateTime? PublishTime { get; set; } // 发布时间
public DateTime ActivityEndTime { get; set; } // 活动结束时间
public string ActivityButtonText { get; set; } // 按钮文本
}
9.5.2 检查用户参与状态接口(用户参与状态应该是在系统消息接口返回的时候就应该带上这个状态的,因为没有消息详情,所以列表中就是全部消息内容)
接口:GET /api/Messages/CheckActivityParticipated
请求参数:
public class CheckActivityParticipatedReq
{
public long NotificationId { get; set; } // 通知ID
}
返回结果:
public class CheckActivityParticipatedResp
{
public bool IsParticipated { get; set; } // 是否已参与
public DateTime? SubmittedAt { get; set; } // 参与时间(如果已参与)
}
9.5.3 参与活动接口
接口:POST /api/Messages/ParticipateActivity
请求参数:
public class ParticipateActivityReq
{
public long NotificationId { get; set; } // 通知ID
public string ContactInfo { get; set; } // 联系方式
public string ShippingAddress { get; set; } // 收货地址
}
9.6 前端展示逻辑
9.6.1 消息列表展示
- 普通系统通知:正常显示标题、内容、时间
- 活动通知:额外显示活动按钮(如果未过期且未参与)
9.6.2 活动按钮状态
-
可参与状态(绿色/正常)
- 条件:
IsActivity == true&&ActivityEndTime > 当前时间&&用户未参与 - 显示:活动按钮文本(如"参与活动")
- 条件:
-
已过期状态(灰色/禁用)
- 条件:
ActivityEndTime <= 当前时间 - 显示:按钮置灰,不可点击
- 条件:
-
已参与状态(隐藏或显示"已参与")
- 条件:用户已参与该活动
- 显示:隐藏按钮或显示"已参与"文字
9.6.3 参与表单弹窗
- 表单字段:
- 联系方式(必填)
- 收货地址(必填)
- 提交后:关闭弹窗,更新按钮状态
9.7 数据查询优化
9.7.1 系统通知创建时的用户筛选
在 EnsureSystemNotificationsCreated 方法中,需要根据通知的 TargetUserType 和 TargetCertifiedType 筛选用户:
// 伪代码逻辑
if (notification.TargetUserType == 1) // 指定用户
{
var userIds = JsonConvert.DeserializeObject<List<long>>(notification.TargetUserIds);
// 只为这些用户创建消息
}
else if (notification.TargetUserType == 2) // 按认证类型
{
// 查询符合认证类型的用户
var users = _usersRepository.Select
.Where(u => u.CertifiedType == notification.TargetCertifiedType)
.ToList();
// 为这些用户创建消息
}
else // 所有用户
{
// 为所有用户创建消息
}
9.7.2 参与记录查询优化
- 使用索引:NotificationId + UserId 联合索引
- 支持按状态筛选、按时间排序
- 支持分页查询
9.8 注意事项
-
数据一致性
- 确保活动通知创建时,目标用户的消息记录正确创建
- 确保用户参与记录的唯一性(一个用户一个活动只能参与一次)
-
性能考虑
- 如果目标用户数量很大(如所有用户),需要考虑批量创建消息的性能,所以我们采用被动式触发创建系统消息。当用户去访问了未读数量,或者系统消息列表接口时候,我们去查询处理一下新的消息接口。
-
安全性
- 参与活动接口需要验证用户身份
- 防止用户重复提交(通过数据库唯一约束)
- 验证活动是否有效(未过期、已发布)
-
扩展性
- 参与状态字段支持后续扩展(已处理、已发货等)暂不需要
- 可以考虑支持更多筛选条件(如VIP用户、等级用户等)暂不需要
9.9 待确认问题
-
发送范围扩展
- 是否支持更多筛选条件?(如VIP用户、等级范围等)暂不需要
- 是否支持组合条件?(如"SK认证且VIP用户")暂不需要
-
参与表单字段
- 联系方式、收货地址的字段长度限制?联系方式字符串限制100,收获地址限制500
- 是否需要其他字段?(如邮编、备注等)不需要
-
活动类型扩展
- 是否只支持"填写收货地址"这一种活动类型?
- 未来是否需要支持其他类型的活动?(如问卷调查、投票等)以后再扩展。
-
消息创建时机
- 活动通知是否采用延迟创建机制,和正常系统通知一样。