fix: align backend APIs and upload flow
This commit is contained in:
@@ -20,24 +20,24 @@ namespace MessageService.WebApi.Controllers.Conversation
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> List()
|
||||
public async Task<IActionResult> List(CancellationToken cancellationToken)
|
||||
{
|
||||
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
|
||||
return Ok(await service.GetByOwnerIdAsync(Guid.Parse(userId)));
|
||||
return Ok(await service.GetByOwnerIdAsync(Guid.Parse(userId!), cancellationToken));
|
||||
}
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> Get([FromQuery]Guid id)
|
||||
public async Task<IActionResult> Get([FromQuery]Guid id, CancellationToken cancellationToken)
|
||||
{
|
||||
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
|
||||
return Ok(await service.GetByIdAsync(id, Guid.Parse(userId)));
|
||||
return Ok(await service.GetByIdAsync(id, Guid.Parse(userId!), cancellationToken));
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[UnitOfWork(typeof(MessageDbContext))]
|
||||
public async Task<IActionResult> MarkRead([FromQuery] Guid conversationId)
|
||||
public async Task<IActionResult> MarkRead([FromQuery] Guid conversationId, long? lastReadSequenceId, CancellationToken cancellationToken)
|
||||
{
|
||||
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
|
||||
return Ok(await service.MarkAsReadAsync(conversationId, Guid.Parse(userId)));
|
||||
return Ok(await service.MarkAsReadAsync(conversationId, Guid.Parse(userId!), lastReadSequenceId, cancellationToken));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
using System.Text.Json;
|
||||
using System.Security.Claims;
|
||||
using IM.InitCommon.Management;
|
||||
using IM.Commons;
|
||||
using MessageService.Infrastructure;
|
||||
using MessageService.Domain.Enums;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace MessageService.WebApi.Controllers;
|
||||
[ApiController]
|
||||
public sealed class ManagementController(MessageDbContext db, InternalClient client) : ControllerBase
|
||||
{
|
||||
[HttpPost("api/message/report"), Authorize]
|
||||
public async Task<IActionResult> Report(ClientReport input, CancellationToken ct)
|
||||
{
|
||||
var reporter = Guid.Parse(User.FindFirstValue(ClaimTypes.NameIdentifier)!);
|
||||
JsonElement result;
|
||||
try { result = await client.Send<JsonElement>("admin", "/internal/management/reports", new { ReporterId = reporter, input.Type, input.TargetId, input.Reason, input.Description, input.MessageIds }, ct); }
|
||||
catch (InternalServiceException e) { return StatusCode(e.Status, ManagementResult.Fail(e.Status switch { 429 => "举报次数已达上限或仍在重复举报冷却期", 403 => "没有访问举报对象或消息的权限", 400 => "举报内容无效,请检查分类和关联消息", _ => "举报服务暂不可用,请重试" })); }
|
||||
return Ok(ManagementResult.Ok(result));
|
||||
}
|
||||
[HttpPost("internal/management/evidence")]
|
||||
public async Task<IActionResult> Evidence(EvidenceRequest input, CancellationToken ct)
|
||||
{
|
||||
if (input.Type is not "user" and not "group" || input.MessageIds.Length > 20 || input.TargetId == input.ReporterId) return BadRequest();
|
||||
string name;
|
||||
if (input.Type == "group") {
|
||||
var access = await client.Send<JsonElement>("group", $"/internal/management/access/{input.TargetId}/{input.ReporterId}", ct: ct);
|
||||
if (!access.GetProperty("member").GetBoolean()) return Forbid();
|
||||
var group = await client.Send<JsonElement>("group", $"/internal/management/detail/{input.TargetId}", ct: ct); name = group.GetProperty("name").GetString()!;
|
||||
} else {
|
||||
var relation = await client.Send<JsonElement>("contact", $"/internal/management/relation/{input.ReporterId}/{input.TargetId}", ct: ct);
|
||||
if (!relation.GetProperty("related").GetBoolean() && input.MessageIds.Length == 0) return Forbid();
|
||||
var user = await client.Send<JsonElement>("user", $"/internal/management/list?q={input.TargetId}&size=1", ct: ct);
|
||||
if (user.GetProperty("items").GetArrayLength() == 0) return NotFound(); name = user.GetProperty("items")[0].GetProperty("name").GetString()!;
|
||||
}
|
||||
var ids = input.MessageIds.Distinct().ToArray(); var messages = await db.Messages.AsNoTracking().Where(x => ids.Contains(x.Id)).ToListAsync(ct);
|
||||
if (messages.Count != ids.Length) return BadRequest();
|
||||
var evidence = new List<EvidenceSnapshot>();
|
||||
foreach (var m in messages) {
|
||||
var ownConversation = await db.Conversations.AnyAsync(x => x.UserId == input.ReporterId && x.StreamKey == m.StreamKey, ct);
|
||||
if (!ownConversation || (input.Type == "user" && m.SenderId != input.TargetId) || (input.Type == "group" && (m.ChatType != ChatType.GROUP || m.TargetId != input.TargetId))) return Forbid();
|
||||
if (m.ChatType == ChatType.GROUP) { var access = await client.Send<JsonElement>("group", $"/internal/management/access/{m.TargetId}/{input.ReporterId}", ct: ct); if (!access.GetProperty("member").GetBoolean()) return Forbid(); }
|
||||
using var body = JsonDocument.Parse(m.Content.RawBody ?? "{}"); Guid? fileId = null;
|
||||
if ((body.RootElement.TryGetProperty("FileId", out var file) || body.RootElement.TryGetProperty("fileId", out file)) && file.ValueKind == JsonValueKind.String && file.TryGetGuid(out var fid)) fileId = fid;
|
||||
evidence.Add(new(m.Id, m.SenderId, m.SenderId.ToString(), m.Content.Fallback, m.MsgType.ToString(), fileId, m.CreationTime));
|
||||
}
|
||||
return Ok(new SubjectEvidence(name, evidence));
|
||||
}
|
||||
}
|
||||
public record ClientReport(string Type, Guid TargetId, string Reason, string Description, Guid[] MessageIds);
|
||||
@@ -37,11 +37,24 @@ namespace MessageService.WebApi.Controllers.Message
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> GetMessages([FromQuery]Guid conversationId, long? cursor, int direction, int limit)
|
||||
public async Task<IActionResult> GetMessages([FromQuery]Guid conversationId, long? cursor, int direction, int limit, CancellationToken cancellationToken)
|
||||
{
|
||||
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
|
||||
var command = new GetMessageCommand(conversationId, Guid.Parse(userId), cursor, direction, limit);
|
||||
return Ok(await service.GetMessagesAsync(command));
|
||||
return Ok(await service.GetMessagesAsync(command, cancellationToken));
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> Search(
|
||||
[FromQuery] Guid conversationId,
|
||||
[FromQuery] string keyword,
|
||||
long? cursor,
|
||||
int limit = 30,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
|
||||
var command = new SearchMessageCommand(conversationId, Guid.Parse(userId!), keyword, cursor, limit);
|
||||
return Ok(await service.SearchMessagesAsync(command, cancellationToken));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,10 @@ namespace MessageService.WebApi.Controllers.Message
|
||||
public int? Height { get; init; }
|
||||
public string? Thumb { get; init; }
|
||||
public int? Duration { get; init; }
|
||||
public Guid? FileId { get; init; }
|
||||
public string? FileName { get; init; }
|
||||
public long? FileSize { get; init; }
|
||||
public string? FileFormat { get; init; }
|
||||
|
||||
public SendMsgCommand ToCommand(Guid senderId)
|
||||
{
|
||||
@@ -39,7 +43,11 @@ namespace MessageService.WebApi.Controllers.Message
|
||||
Width,
|
||||
Height,
|
||||
Thumb,
|
||||
Duration
|
||||
Duration,
|
||||
FileId,
|
||||
FileName,
|
||||
FileSize,
|
||||
FileFormat
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -63,9 +71,19 @@ namespace MessageService.WebApi.Controllers.Message
|
||||
|
||||
// 图片/视频/语音消息:url 必填
|
||||
When(r => r.MsgType == MessageType.Image || r.MsgType == MessageType.Video
|
||||
|| r.MsgType == MessageType.Voice || r.MsgType == MessageType.File, () =>
|
||||
|| r.MsgType == MessageType.Voice, () =>
|
||||
{
|
||||
RuleFor(r => r.Url).NotEmpty().WithMessage("媒体消息的 url 字段不能为空");
|
||||
RuleFor(r => r)
|
||||
.Must(request => !string.IsNullOrWhiteSpace(request.Url) || request.FileId.HasValue)
|
||||
.WithMessage("媒体消息必须提供 url 或 fileId");
|
||||
});
|
||||
|
||||
When(r => r.MsgType == MessageType.File, () =>
|
||||
{
|
||||
RuleFor(r => r.FileId).NotNull().NotEmpty().WithMessage("文件消息的 fileId 字段不能为空");
|
||||
RuleFor(r => r.FileName).NotEmpty().WithMessage("文件消息的 fileName 字段不能为空");
|
||||
RuleFor(r => r.FileSize).NotNull().GreaterThanOrEqualTo(0).WithMessage("文件消息的 fileSize 字段不合法");
|
||||
RuleFor(r => r.FileFormat).NotEmpty().WithMessage("文件消息的 fileFormat 字段不能为空");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user