统一通知中心
This commit is contained in:
@@ -0,0 +1,161 @@
|
||||
using System.Security.Claims;
|
||||
using Jiaowu.Api.Domain.Academic;
|
||||
using Jiaowu.Api.Domain.Identity;
|
||||
using Jiaowu.Api.Infrastructure.Auth;
|
||||
using Jiaowu.Api.Infrastructure.Persistence;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Jiaowu.Api.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Authorize]
|
||||
[Route("api/notifications")]
|
||||
public sealed class NotificationsController(
|
||||
AppDbContext db,
|
||||
ICurrentUserDataScope currentUserDataScope) : ControllerBase
|
||||
{
|
||||
[HttpGet]
|
||||
public async Task<ActionResult> GetNotifications(
|
||||
bool? unreadOnly,
|
||||
int page = 1,
|
||||
int pageSize = 20,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var userId = currentUserDataScope.Current.UserId;
|
||||
var source = db.Notifications.AsNoTracking()
|
||||
.Where(x => x.UserId == userId);
|
||||
if (unreadOnly == true)
|
||||
source = source.Where(x => !x.IsRead);
|
||||
|
||||
var total = await source.CountAsync(cancellationToken);
|
||||
var items = await source
|
||||
.OrderByDescending(x => x.CreatedAt)
|
||||
.Skip((page - 1) * pageSize)
|
||||
.Take(pageSize)
|
||||
.Select(x => new
|
||||
{
|
||||
x.Id, x.Title, x.Content, x.IsRead, x.LinkUrl, x.CreatedAt
|
||||
})
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
var unreadCount = await db.Notifications
|
||||
.CountAsync(x => x.UserId == userId && !x.IsRead, cancellationToken);
|
||||
|
||||
return Ok(new { Items = items, Total = total, UnreadCount = unreadCount });
|
||||
}
|
||||
|
||||
[HttpGet("unread-count")]
|
||||
public async Task<ActionResult> GetUnreadCount(CancellationToken cancellationToken)
|
||||
{
|
||||
var userId = currentUserDataScope.Current.UserId;
|
||||
var count = await db.Notifications
|
||||
.CountAsync(x => x.UserId == userId && !x.IsRead, cancellationToken);
|
||||
return Ok(new { Count = count });
|
||||
}
|
||||
|
||||
[HttpPost("{id:guid}/read")]
|
||||
public async Task<ActionResult> MarkRead(Guid id, CancellationToken cancellationToken)
|
||||
{
|
||||
var userId = currentUserDataScope.Current.UserId;
|
||||
var n = await db.Notifications
|
||||
.FirstOrDefaultAsync(x => x.Id == id && x.UserId == userId, cancellationToken);
|
||||
if (n is null) return NotFound();
|
||||
n.IsRead = true;
|
||||
await db.SaveChangesAsync(cancellationToken);
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
[HttpPost("read-all")]
|
||||
public async Task<ActionResult> MarkAllRead(CancellationToken cancellationToken)
|
||||
{
|
||||
var userId = currentUserDataScope.Current.UserId;
|
||||
await db.Notifications
|
||||
.Where(x => x.UserId == userId && !x.IsRead)
|
||||
.ExecuteUpdateAsync(s => s.SetProperty(x => x.IsRead, true),
|
||||
cancellationToken);
|
||||
return NoContent();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Centralized helper to send notifications across the app.
|
||||
/// </summary>
|
||||
public static class NotificationService
|
||||
{
|
||||
public static async Task SendAsync(
|
||||
AppDbContext db,
|
||||
Guid userId,
|
||||
string title,
|
||||
string content,
|
||||
string? linkUrl = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
db.Notifications.Add(new Notification
|
||||
{
|
||||
UserId = userId,
|
||||
Title = title,
|
||||
Content = content,
|
||||
LinkUrl = linkUrl
|
||||
});
|
||||
await db.SaveChangesAsync(cancellationToken);
|
||||
}
|
||||
|
||||
public static async Task SendToRoleAsync(
|
||||
AppDbContext db,
|
||||
string roleName,
|
||||
string title,
|
||||
string content,
|
||||
Guid? collegeId = null,
|
||||
string? linkUrl = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var query = db.Users
|
||||
.Join(db.UserRoles, u => u.Id, ur => ur.UserId, (u, ur) => new { u.Id, ur.RoleId })
|
||||
.Join(db.Roles, x => x.RoleId, r => r.Id, (x, r) => new { x.Id, RoleName = r.Name! });
|
||||
|
||||
var userIds = query.Where(x => x.RoleName == roleName);
|
||||
|
||||
if (collegeId.HasValue && roleName == SystemRoles.CollegeAdmin)
|
||||
{
|
||||
userIds = userIds.Where(x =>
|
||||
db.Teachers.Any(t =>
|
||||
t.UserId == x.Id && t.CollegeId == collegeId.Value));
|
||||
}
|
||||
|
||||
var ids = await userIds.Select(x => x.Id).Distinct().ToListAsync(cancellationToken);
|
||||
foreach (var id in ids)
|
||||
{
|
||||
db.Notifications.Add(new Notification
|
||||
{
|
||||
UserId = id,
|
||||
Title = title,
|
||||
Content = content,
|
||||
LinkUrl = linkUrl
|
||||
});
|
||||
}
|
||||
await db.SaveChangesAsync(cancellationToken);
|
||||
}
|
||||
|
||||
public static async Task SendToUserIdsAsync(
|
||||
AppDbContext db,
|
||||
IEnumerable<Guid> userIds,
|
||||
string title,
|
||||
string content,
|
||||
string? linkUrl = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
foreach (var userId in userIds.Distinct())
|
||||
{
|
||||
db.Notifications.Add(new Notification
|
||||
{
|
||||
UserId = userId,
|
||||
Title = title,
|
||||
Content = content,
|
||||
LinkUrl = linkUrl
|
||||
});
|
||||
}
|
||||
await db.SaveChangesAsync(cancellationToken);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user