后端:

add(GroupNotification):合并群通知表
This commit is contained in:
2026-03-15 14:55:29 +08:00
parent f7223dc590
commit eb8455e141
70 changed files with 3534 additions and 660 deletions
@@ -15,7 +15,7 @@ namespace IM_API.Application.EventHandlers.GroupInviteActionUpdateHandler
public async Task Consume(ConsumeContext<GroupInviteActionUpdateEvent> context)
{
var @event = context.Message;
if(@event.Action == Models.GroupInviteState.Passed)
if(@event.Action == Models.GroupRequestState.Passed)
{
await _groupService.MakeGroupRequestAsync(@event.UserId, @event.InviteUserId,@event.GroupId);
}
+14
View File
@@ -229,6 +229,20 @@ namespace IM_API.Configs
.ForMember(dest => dest.AllMembersBanned, opt => opt.MapFrom(src => src.AllMembersBannedEnum))
.ForMember(dest => dest.Status, opt => opt.MapFrom(src => src.StatusEnum))
;
//群通知模型转换
CreateMap<GroupRequest, GroupNotificationVo>()
.ForMember(dest => dest.UserId, opt => opt.MapFrom(src => src.UserId))
.ForMember(dest => dest.GroupId, opt => opt.MapFrom(src => src.GroupId))
.ForMember(dest => dest.InviteUser, opt => opt.MapFrom(src => src.InviteUserId))
.ForMember(dest => dest.Status, opt => opt.MapFrom(src => src.StateEnum))
.ForMember(dest => dest.Description, opt => opt.MapFrom(src => src.Description))
.ForMember(dest => dest.RequestId, opt => opt.MapFrom(src => src.Id))
//.ForAllMembers(opt => opt.Ignore())
;
}
}
}
@@ -96,5 +96,14 @@ namespace IM_API.Controllers
var group = await _groupService.GetGroupInfoAsync(groupId);
return Ok(new BaseResponse<GroupInfoVo>(group));
}
[HttpGet]
[ProducesResponseType(typeof(BaseResponse<List<GroupNotificationVo>>), StatusCodes.Status200OK)]
public async Task<IActionResult> GetGroupNotification([FromQuery]int groupId)
{
string userIdStr = User.FindFirstValue(ClaimTypes.NameIdentifier)!;
var data = await _groupService.GetGroupNotificationAsync(int.Parse(userIdStr));
return Ok(new BaseResponse<List<GroupNotificationVo>>(data));
}
}
}
@@ -9,6 +9,6 @@ namespace IM_API.Domain.Events
public int InviteUserId { get; set; }
public int InviteId { get; set; }
public int GroupId { get; set; }
public GroupInviteState Action { get; set; }
public GroupRequestState Action { get; set; }
}
}
@@ -5,6 +5,6 @@ namespace IM_API.Dtos.Group
public class HandleGroupInviteDto
{
public int InviteId { get; set; }
public GroupInviteState Action { get; set; }
public GroupRequestState Action { get; set; }
}
}
@@ -56,5 +56,12 @@ namespace IM_API.Interface.Services
Task<GroupInfoVo> UpdateGroupInfoAsync(int userId, int groupId, GroupUpdateDto updateDto);
Task<GroupInfoVo> GetGroupInfoAsync(int groupId);
/// <summary>
/// 获取群聊通知
/// </summary>
/// <param name="userId"></param>
/// <returns></returns>
Task<List<GroupNotificationVo>> GetGroupNotificationAsync(int userId);
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,54 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace IM_API.Migrations
{
/// <inheritdoc />
public partial class updategroupannouncement : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.UpdateData(
table: "groups",
keyColumn: "Announcement",
keyValue: null,
column: "Announcement",
value: "");
migrationBuilder.AlterColumn<string>(
name: "Announcement",
table: "groups",
type: "text",
nullable: false,
comment: "群公告",
collation: "utf8mb4_general_ci",
oldClrType: typeof(string),
oldType: "text",
oldNullable: true,
oldComment: "群公告")
.Annotation("MySql:CharSet", "utf8mb4")
.OldAnnotation("MySql:CharSet", "utf8mb4")
.OldAnnotation("Relational:Collation", "utf8mb4_general_ci");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterColumn<string>(
name: "Announcement",
table: "groups",
type: "text",
nullable: true,
comment: "群公告",
collation: "utf8mb4_general_ci",
oldClrType: typeof(string),
oldType: "text",
oldComment: "群公告")
.Annotation("MySql:CharSet", "utf8mb4")
.OldAnnotation("MySql:CharSet", "utf8mb4")
.OldAnnotation("Relational:Collation", "utf8mb4_general_ci");
}
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,87 @@
using System;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace IM_API.Migrations
{
/// <inheritdoc />
public partial class groupinviterequestmerge : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "group_invite");
migrationBuilder.AddColumn<int>(
name: "InviteUserId",
table: "group_request",
type: "int",
nullable: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "InviteUserId",
table: "group_request");
migrationBuilder.RenameIndex(
name: "GroupId",
table: "group_request",
newName: "GroupId1");
migrationBuilder.CreateTable(
name: "group_invite",
columns: table => new
{
ID = table.Column<int>(type: "int(11)", nullable: false)
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
GroupId = table.Column<int>(type: "int(11)", nullable: false, comment: "群聊编号"),
InvitedUser = table.Column<int>(type: "int(11)", nullable: true, comment: "被邀请用户"),
InviteUser = table.Column<int>(type: "int(11)", nullable: true, comment: "邀请用户"),
Created = table.Column<DateTimeOffset>(type: "datetime", nullable: true, comment: "创建时间"),
State = table.Column<sbyte>(type: "tinyint(4)", nullable: true, comment: "当前状态(0:待被邀请人同意\r\n1:被邀请人已同意)")
},
constraints: table =>
{
table.PrimaryKey("PRIMARY", x => x.ID);
table.ForeignKey(
name: "group_invite_ibfk_1",
column: x => x.InviteUser,
principalTable: "users",
principalColumn: "ID");
table.ForeignKey(
name: "group_invite_ibfk_2",
column: x => x.GroupId,
principalTable: "groups",
principalColumn: "ID");
table.ForeignKey(
name: "group_invite_ibfk_3",
column: x => x.InvitedUser,
principalTable: "users",
principalColumn: "ID");
})
.Annotation("MySql:CharSet", "utf8mb4")
.Annotation("Relational:Collation", "utf8mb4_general_ci");
migrationBuilder.CreateIndex(
name: "GroupId",
table: "group_invite",
column: "GroupId");
migrationBuilder.CreateIndex(
name: "InvitedUser",
table: "group_invite",
column: "InvitedUser");
migrationBuilder.CreateIndex(
name: "InviteUser",
table: "group_invite",
column: "InviteUser");
}
}
}
@@ -333,6 +333,7 @@ namespace IM_API.Migrations
.HasComment("全员禁言(0允许发言,2全员禁言)");
b.Property<string>("Announcement")
.IsRequired()
.HasColumnType("text")
.HasComment("群公告");
@@ -392,50 +393,6 @@ namespace IM_API.Migrations
MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci");
});
modelBuilder.Entity("IM_API.Models.GroupInvite", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int(11)")
.HasColumnName("ID");
MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property<int>("Id"));
b.Property<DateTimeOffset?>("Created")
.HasColumnType("datetime")
.HasComment("创建时间");
b.Property<int>("GroupId")
.HasColumnType("int(11)")
.HasComment("群聊编号");
b.Property<int?>("InviteUser")
.HasColumnType("int(11)")
.HasComment("邀请用户");
b.Property<int?>("InvitedUser")
.HasColumnType("int(11)")
.HasComment("被邀请用户");
b.Property<sbyte?>("State")
.HasColumnType("tinyint(4)")
.HasComment("当前状态(0:待被邀请人同意\r\n1:被邀请人已同意)");
b.HasKey("Id")
.HasName("PRIMARY");
b.HasIndex(new[] { "GroupId" }, "GroupId");
b.HasIndex(new[] { "InviteUser" }, "InviteUser");
b.HasIndex(new[] { "InvitedUser" }, "InvitedUser");
b.ToTable("group_invite", (string)null);
MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4");
MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci");
});
modelBuilder.Entity("IM_API.Models.GroupMember", b =>
{
b.Property<int>("Id")
@@ -502,6 +459,9 @@ namespace IM_API.Migrations
.HasColumnType("int(11)")
.HasComment("群聊编号\r\n");
b.Property<int?>("InviteUserId")
.HasColumnType("int");
b.Property<sbyte>("State")
.HasColumnType("tinyint(4)")
.HasComment("申请状态(0:待管理员同意,1:已拒绝,2:已同意)");
@@ -515,8 +475,7 @@ namespace IM_API.Migrations
b.HasIndex("UserId");
b.HasIndex(new[] { "GroupId" }, "GroupId")
.HasDatabaseName("GroupId1");
b.HasIndex(new[] { "GroupId" }, "GroupId");
b.ToTable("group_request", (string)null);
@@ -991,31 +950,6 @@ namespace IM_API.Migrations
b.Navigation("GroupMasterNavigation");
});
modelBuilder.Entity("IM_API.Models.GroupInvite", b =>
{
b.HasOne("IM_API.Models.Group", "Group")
.WithMany("GroupInvites")
.HasForeignKey("GroupId")
.IsRequired()
.HasConstraintName("group_invite_ibfk_2");
b.HasOne("IM_API.Models.User", "InviteUserNavigation")
.WithMany("GroupInviteInviteUserNavigations")
.HasForeignKey("InviteUser")
.HasConstraintName("group_invite_ibfk_1");
b.HasOne("IM_API.Models.User", "InvitedUserNavigation")
.WithMany("GroupInviteInvitedUserNavigations")
.HasForeignKey("InvitedUser")
.HasConstraintName("group_invite_ibfk_3");
b.Navigation("Group");
b.Navigation("InviteUserNavigation");
b.Navigation("InvitedUserNavigation");
});
modelBuilder.Entity("IM_API.Models.GroupMember", b =>
{
b.HasOne("IM_API.Models.Group", "Group")
@@ -1108,8 +1042,6 @@ namespace IM_API.Migrations
modelBuilder.Entity("IM_API.Models.Group", b =>
{
b.Navigation("GroupInvites");
b.Navigation("GroupMembers");
b.Navigation("GroupRequests");
@@ -1148,10 +1080,6 @@ namespace IM_API.Migrations
b.Navigation("FriendUsers");
b.Navigation("GroupInviteInviteUserNavigations");
b.Navigation("GroupInviteInvitedUserNavigations");
b.Navigation("GroupMembers");
b.Navigation("GroupRequests");
+1 -2
View File
@@ -38,7 +38,7 @@ public partial class Group
/// <summary>
/// 群公告
/// </summary>
public string? Announcement { get; set; }
public string Announcement { get; set; } = "暂无群公告,点击编辑添加。";
/// <summary>
/// 群聊创建时间
@@ -56,7 +56,6 @@ public partial class Group
public DateTimeOffset LastUpdateTime { get; set; } = DateTime.UtcNow;
public virtual ICollection<GroupInvite> GroupInvites { get; set; } = new List<GroupInvite>();
public virtual User GroupMasterNavigation { get; set; } = null!;
-11
View File
@@ -1,11 +0,0 @@
namespace IM_API.Models
{
public partial class GroupInvite
{
public GroupInviteState StateEnum
{
get => (GroupInviteState)State;
set => State = (sbyte)value;
}
}
}
-17
View File
@@ -1,17 +0,0 @@
namespace IM_API.Models
{
/// <summary>
/// 群邀请状态
/// </summary>
public enum GroupInviteState
{
/// <summary>
/// 待处理
/// </summary>
Pending = 0,
/// <summary>
/// 已同意
/// </summary>
Passed = 1
}
}
+11 -3
View File
@@ -7,12 +7,20 @@
/// </summary>
Pending = 0,
/// <summary>
/// 已拒绝
/// 管理员已拒绝
/// </summary>
Declined = 1,
/// <summary>
/// 已同意
/// 管理员已同意
/// </summary>
Passed = 2
Passed = 2,
/// <summary>
/// 待对方同意
/// </summary>
TargetPending = 3,
/// <summary>
/// 对方拒绝
/// </summary>
TargetDeclined = 4
}
}
-41
View File
@@ -1,41 +0,0 @@
using System;
using System.Collections.Generic;
namespace IM_API.Models;
public partial class GroupInvite
{
public int Id { get; set; }
/// <summary>
/// 群聊编号
/// </summary>
public int GroupId { get; set; }
/// <summary>
/// 被邀请用户
/// </summary>
public int? InvitedUser { get; set; }
/// <summary>
/// 邀请用户
/// </summary>
public int? InviteUser { get; set; }
/// <summary>
/// 当前状态(0:待被邀请人同意
/// 1:被邀请人已同意)
/// </summary>
public sbyte? State { get; set; }
/// <summary>
/// 创建时间
/// </summary>
public DateTimeOffset? Created { get; set; }
public virtual Group Group { get; set; } = null!;
public virtual User? InviteUserNavigation { get; set; }
public virtual User? InvitedUserNavigation { get; set; }
}
+3 -1
View File
@@ -19,8 +19,10 @@ public partial class GroupRequest
/// </summary>
public int UserId { get; set; }
public int? InviteUserId { get; set; }
/// <summary>
/// 申请状态(0:待管理员同意,1:已拒绝,2:已同意
/// 申请状态(0:待管理员同意,1:管理员已拒绝,2管理员已同意,3:待对方同意,4:对方拒绝
/// </summary>
public sbyte State { get; set; }
@@ -49,11 +49,6 @@ namespace IM_API.Models
entity.Ignore(e => e.AuhorityEnum);
});
modelBuilder.Entity<GroupInvite>(entity =>
{
entity.Ignore(e => e.StateEnum);
});
modelBuilder.Entity<GroupMember>(entity =>
{
entity.Ignore(e => e.RoleEnum);
-48
View File
@@ -27,7 +27,6 @@ public partial class ImContext : DbContext
public virtual DbSet<Group> Groups { get; set; }
public virtual DbSet<GroupInvite> GroupInvites { get; set; }
public virtual DbSet<GroupMember> GroupMembers { get; set; }
@@ -362,53 +361,6 @@ public partial class ImContext : DbContext
.HasConstraintName("groups_ibfk_1");
});
modelBuilder.Entity<GroupInvite>(entity =>
{
entity.HasKey(e => e.Id).HasName("PRIMARY");
entity
.ToTable("group_invite")
.HasCharSet("utf8mb4")
.UseCollation("utf8mb4_general_ci");
entity.HasIndex(e => e.GroupId, "GroupId");
entity.HasIndex(e => e.InviteUser, "InviteUser");
entity.HasIndex(e => e.InvitedUser, "InvitedUser");
entity.Property(e => e.Id)
.HasColumnType("int(11)")
.HasColumnName("ID");
entity.Property(e => e.Created)
.HasComment("创建时间")
.HasColumnType("datetime");
entity.Property(e => e.GroupId)
.HasComment("群聊编号")
.HasColumnType("int(11)");
entity.Property(e => e.InviteUser)
.HasComment("邀请用户")
.HasColumnType("int(11)");
entity.Property(e => e.InvitedUser)
.HasComment("被邀请用户")
.HasColumnType("int(11)");
entity.Property(e => e.State)
.HasComment("当前状态(0:待被邀请人同意\r\n1:被邀请人已同意)")
.HasColumnType("tinyint(4)");
entity.HasOne(d => d.Group).WithMany(p => p.GroupInvites)
.HasForeignKey(d => d.GroupId)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("group_invite_ibfk_2");
entity.HasOne(d => d.InviteUserNavigation).WithMany(p => p.GroupInviteInviteUserNavigations)
.HasForeignKey(d => d.InviteUser)
.HasConstraintName("group_invite_ibfk_1");
entity.HasOne(d => d.InvitedUserNavigation).WithMany(p => p.GroupInviteInvitedUserNavigations)
.HasForeignKey(d => d.InvitedUser)
.HasConstraintName("group_invite_ibfk_3");
});
modelBuilder.Entity<GroupMember>(entity =>
{
-4
View File
@@ -73,10 +73,6 @@ public partial class User
[JsonIgnore]
public virtual ICollection<Friend> FriendUsers { get; set; } = new List<Friend>();
[JsonIgnore]
public virtual ICollection<GroupInvite> GroupInviteInviteUserNavigations { get; set; } = new List<GroupInvite>();
[JsonIgnore]
public virtual ICollection<GroupInvite> GroupInviteInvitedUserNavigations { get; set; } = new List<GroupInvite>();
[JsonIgnore]
public virtual ICollection<GroupMember> GroupMembers { get; set; } = new List<GroupMember>();
[JsonIgnore]
public virtual ICollection<GroupRequest> GroupRequests { get; set; } = new List<GroupRequest>();
+79 -10
View File
@@ -79,6 +79,7 @@ namespace IM_API.Services
}
}
public Task DeleteGroupAsync(int userId, int groupId)
{
throw new NotImplementedException();
@@ -90,15 +91,15 @@ namespace IM_API.Services
x => x.Id == groupId) ?? throw new BaseException(CodeDefine.GROUP_NOT_FOUND);
//过滤非好友
var groupInviteIds = await validFriendshipAsync(userId, userIds);
var inviteList = groupInviteIds.Select(id => new GroupInvite
var inviteList = groupInviteIds.Select(id => new GroupRequest
{
Created = DateTime.UtcNow,
GroupId = group.Id,
InviteUser = userId,
InvitedUser = id,
StateEnum = GroupInviteState.Pending
UserId = id,
InviteUserId = userId,
StateEnum = GroupRequestState.TargetPending
}).ToList();
_context.GroupInvites.AddRange(inviteList);
_context.GroupRequests.AddRange(inviteList);
await _context.SaveChangesAsync();
await _endPoint.Publish(new GroupInviteEvent
{
@@ -132,7 +133,7 @@ namespace IM_API.Services
throw new NotImplementedException();
}
public async Task<List<GroupInfoDto>> GetGroupListAsync(int userId, int page, int limit, bool desc)
public async Task<List<GroupInfoDto>> GetGroupListAsync(int userId, int page = 1, int limit = 50, bool desc = false)
{
var query = _context.GroupMembers
.Where(x => x.UserId == userId)
@@ -159,14 +160,18 @@ namespace IM_API.Services
_context.Groups.Update(group);
await _context.SaveChangesAsync();
}
public async Task HandleGroupInviteAsync(int userid, HandleGroupInviteDto dto)
{
var user = _userService.GetUserInfoAsync(userid);
var inviteInfo = await _context.GroupInvites.FirstOrDefaultAsync(x => x.Id == dto.InviteId)
var inviteInfo = await _context.GroupRequests
.FirstOrDefaultAsync(x => x.UserId == userid && x.StateEnum == GroupRequestState.TargetPending)
?? throw new BaseException(CodeDefine.INVALID_ACTION);
if (inviteInfo.InvitedUser != userid) throw new BaseException(CodeDefine.AUTH_FAILED);
if (!(dto.Action == GroupRequestState.TargetPending ||
dto.Action == GroupRequestState.TargetDeclined))
return;
inviteInfo.StateEnum = dto.Action;
_context.GroupInvites.Update(inviteInfo);
_context.GroupRequests.Update(inviteInfo);
await _context.SaveChangesAsync();
await _endPoint.Publish(new GroupInviteActionUpdateEvent
{
@@ -176,12 +181,13 @@ namespace IM_API.Services
EventId = Guid.NewGuid(),
GroupId = inviteInfo.GroupId,
InviteId = inviteInfo.Id,
InviteUserId = inviteInfo.InviteUser.Value,
InviteUserId = inviteInfo.InviteUserId!.Value,
OperatorId = userid,
UserId = userid
});
}
public async Task HandleGroupRequestAsync(int userid, HandleGroupRequestDto dto)
{
var user = _userService.GetUserInfoAsync(userid);
@@ -302,5 +308,68 @@ namespace IM_API.Services
return _mapper.Map<GroupInfoVo>(groupInfo);
}
public async Task<List<GroupNotificationVo>> GetGroupNotificationAsync(int userId)
{
// 1. 查询群请求记录
var groupList = await _context.GroupMembers
.Where(x => x.UserId == userId &&
(x.Role == (sbyte)GroupMemberRole.Master || x.Role == (sbyte)GroupMemberRole.Administrator))
.Select(s => s.GroupId)
.ToListAsync();
var groupRequest = await _context.GroupRequests
.Where(x => groupList.Contains(x.GroupId) || x.UserId == userId || x.InviteUserId == userId)
.OrderByDescending(o => o.Id)
.ToListAsync();
if (!groupRequest.Any()) return new List<GroupNotificationVo>();
// 2. 收集所有需要的 ID 并去重
var userIds = groupRequest.Select(s => s.UserId).Distinct().ToList();
var inviteUserIds = groupRequest.Where(x => x.InviteUserId != null).Select(s => s.InviteUserId.Value).Distinct().ToList();
var groupIds = groupRequest.Select(s => s.GroupId).Distinct().ToList();
var userList = await _userService.GetUserInfoListAsync(userIds);
var inviteUserList = await _userService.GetUserInfoListAsync(inviteUserIds);
var groupInfoList = await _context.Groups
.Where(x => groupIds.Contains(x.Id))
.ToListAsync();
// 2. 转换为字典
var userDict = userList.ToDictionary(u => u.Id);
var inviteUserDict = inviteUserList.ToDictionary(u => u.Id);
var groupDict = groupInfoList.ToDictionary(g => g.Id);
// 3. 组装数据 (Select 逻辑不变)
return groupRequest.Select(g =>
{
var gnv = _mapper.Map<GroupNotificationVo>(g);
// 匹配用户信息
if (userDict.TryGetValue(g.UserId, out var u))
{
gnv.UserAvatar = u.Avatar;
gnv.NickName = u.NickName;
}
// 匹配邀请人信息
if (g.InviteUserId.HasValue && inviteUserDict.TryGetValue(g.InviteUserId.Value, out var i))
{
gnv.InviteUserAvatar = i.Avatar;
gnv.InviteUserNickname = i.NickName;
}
// 匹配群信息
if (groupDict.TryGetValue(g.GroupId, out var gi))
{
gnv.GroupAvatar = gi.Avatar;
gnv.GroupName = gi.Name;
}
return gnv;
}).ToList();
}
}
}
@@ -8,6 +8,6 @@ namespace IM_API.VOs.Group
public int InviteUserId { get; set; }
public int InvitedUserId { get; set; }
public int InviteId { get; set; }
public GroupInviteState Action { get; set; }
public GroupRequestState Action { get; set; }
}
}
@@ -0,0 +1,21 @@
using IM_API.Models;
namespace IM_API.VOs.Group
{
public class GroupNotificationVo
{
public int RequestId { get; set; }
public int? UserId { get; set; }
public string? NickName { get; set; }
public string? UserAvatar { get; set; }
public int GroupId { get; set; }
public string? GroupAvatar { get; set; }
public string? GroupName { get; set; }
public GroupRequestState Status { get; set; }
public string Description { get; set; }
public int? InviteUser { get; set; }
public string? InviteUserNickname { get; set; }
public string? InviteUserAvatar { get; set; }
}
}