Files
backend/internal/dto/message_converter.go
lafay cb85c62093
All checks were successful
Build Backend / build (push) Successful in 2m32s
Build Backend / build-docker (push) Successful in 6m22s
feat(admin): add admin endpoint to list user devices with JPush registration IDs
- Add AdminDeviceTokenResponse DTO including push_token (RegistrationID) for push debugging
- Add DeviceTokenToAdminResponse and DeviceTokensToAdminResponse converters
- Inject PushService into AdminUserHandler for retrieving user devices
- Register GET /api/v1/admin/users/:id/devices route for querying target user's device tokens
2026-06-18 20:07:24 +08:00

233 lines
7.6 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package dto
import (
"with_you/internal/model"
)
// ==================== Message 转换 ====================
// ConvertMessageToResponse 将Message转换为MessageResponse
func ConvertMessageToResponse(message *model.Message) *MessageResponse {
return ConvertMessageToResponseWithExpiry(message, nil)
}
// ConvertMessageToResponseWithExpiry 将Message转换为MessageResponse并对已过期的 file segment 注入 expired 标记。
// expiredURLSet 为已从 S3 清理的文件 URL 集合(可为 nil表示不标记
// 注意:注入时对 file segment 的 Data 做深拷贝,避免污染消息缓存。
func ConvertMessageToResponseWithExpiry(message *model.Message, expiredURLSet map[string]struct{}) *MessageResponse {
if message == nil {
return nil
}
// 直接使用 segments不需要解析
segments := make(model.MessageSegments, len(message.Segments))
for i, seg := range message.Segments {
// 对 file 类型且 URL 已过期的 segment深拷贝 Data 并注入 expired 标记
if seg.Type == string(model.ContentTypeFile) && len(expiredURLSet) > 0 {
url, _ := seg.Data["url"].(string)
if _, expired := expiredURLSet[url]; expired {
newData := make(map[string]any, len(seg.Data)+1)
for k, v := range seg.Data {
newData[k] = v
}
newData["expired"] = true
segments[i] = model.MessageSegment{Type: seg.Type, Data: newData}
continue
}
}
segments[i] = model.MessageSegment{
Type: seg.Type,
Data: seg.Data,
}
}
return &MessageResponse{
ID: message.ID,
ConversationID: message.ConversationID,
SenderID: message.SenderID,
Seq: message.Seq,
Segments: segments,
ReplyToID: message.ReplyToID,
Status: string(message.Status),
Category: string(message.Category),
CreatedAt: FormatTime(message.CreatedAt),
}
}
// ConvertConversationToResponse 将Conversation转换为ConversationResponse
// participants: 会话参与者列表(用户信息)
// unreadCount: 当前用户的未读消息数
// lastMessage: 最后一条消息
func ConvertConversationToResponse(conv *model.Conversation, participants []*model.User, unreadCount int, lastMessage *model.Message, isPinned bool, notificationMuted bool) *ConversationResponse {
if conv == nil {
return nil
}
var participantResponses []*UserResponse
for _, p := range participants {
participantResponses = append(participantResponses, ConvertUserToResponse(p))
}
// 转换群组信息
var groupResponse *GroupResponse
if conv.Group != nil {
groupResponse = GroupToResponse(conv.Group)
}
return &ConversationResponse{
ID: conv.ID,
Type: string(conv.Type),
IsPinned: isPinned,
NotificationMuted: notificationMuted,
Group: groupResponse,
LastSeq: conv.LastSeq,
LastMessage: ConvertMessageToResponse(lastMessage),
LastMessageAt: FormatTimePointer(conv.LastMsgTime),
UnreadCount: unreadCount,
Participants: participantResponses,
CreatedAt: FormatTime(conv.CreatedAt),
UpdatedAt: FormatTime(conv.UpdatedAt),
}
}
// ConvertConversationToDetailResponse 将Conversation转换为ConversationDetailResponse
func ConvertConversationToDetailResponse(conv *model.Conversation, participants []*model.User, unreadCount int64, lastMessage *model.Message, myLastReadSeq int64, otherLastReadSeq int64, isPinned bool, notificationMuted bool) *ConversationDetailResponse {
if conv == nil {
return nil
}
var participantResponses []*UserResponse
for _, p := range participants {
participantResponses = append(participantResponses, ConvertUserToResponse(p))
}
return &ConversationDetailResponse{
ID: conv.ID,
Type: string(conv.Type),
IsPinned: isPinned,
NotificationMuted: notificationMuted,
LastSeq: conv.LastSeq,
LastMessage: ConvertMessageToResponse(lastMessage),
LastMessageAt: FormatTimePointer(conv.LastMsgTime),
UnreadCount: unreadCount,
Participants: participantResponses,
MyLastReadSeq: myLastReadSeq,
OtherLastReadSeq: otherLastReadSeq,
CreatedAt: FormatTime(conv.CreatedAt),
UpdatedAt: FormatTime(conv.UpdatedAt),
}
}
// ConvertMessagesToResponse 将Message列表转换为响应列表
func ConvertMessagesToResponse(messages []*model.Message) []*MessageResponse {
result := make([]*MessageResponse, 0, len(messages))
for _, msg := range messages {
result = append(result, ConvertMessageToResponse(msg))
}
return result
}
// ConvertMessagesToResponseWithExpiry 将Message列表转换为响应列表并标记过期文件
func ConvertMessagesToResponseWithExpiry(messages []*model.Message, expiredURLSet map[string]struct{}) []*MessageResponse {
result := make([]*MessageResponse, 0, len(messages))
for _, msg := range messages {
result = append(result, ConvertMessageToResponseWithExpiry(msg, expiredURLSet))
}
return result
}
// ==================== PushRecord 转换 ====================
// PushRecordToResponse 将PushRecord转换为PushRecordResponse
func PushRecordToResponse(record *model.PushRecord) *PushRecordResponse {
if record == nil {
return nil
}
resp := &PushRecordResponse{
ID: record.ID,
MessageID: record.MessageID,
PushChannel: string(record.PushChannel),
PushStatus: string(record.PushStatus),
RetryCount: record.RetryCount,
CreatedAt: record.CreatedAt,
}
if record.PushedAt != nil {
resp.PushedAt = *record.PushedAt
}
if record.DeliveredAt != nil {
resp.DeliveredAt = *record.DeliveredAt
}
return resp
}
// PushRecordsToResponse 将PushRecord列表转换为响应列表
func PushRecordsToResponse(records []*model.PushRecord) []*PushRecordResponse {
result := make([]*PushRecordResponse, 0, len(records))
for _, record := range records {
result = append(result, PushRecordToResponse(record))
}
return result
}
// ==================== DeviceToken 转换 ====================
// DeviceTokenToResponse 将DeviceToken转换为DeviceTokenResponse
func DeviceTokenToResponse(token *model.DeviceToken) *DeviceTokenResponse {
if token == nil {
return nil
}
resp := &DeviceTokenResponse{
ID: token.ID,
DeviceID: token.DeviceID,
DeviceType: string(token.DeviceType),
IsActive: token.IsActive,
DeviceName: token.DeviceName,
CreatedAt: token.CreatedAt,
}
if token.LastUsedAt != nil {
resp.LastUsedAt = *token.LastUsedAt
}
return resp
}
// DeviceTokensToResponse 将DeviceToken列表转换为响应列表
func DeviceTokensToResponse(tokens []*model.DeviceToken) []*DeviceTokenResponse {
result := make([]*DeviceTokenResponse, 0, len(tokens))
for _, token := range tokens {
result = append(result, DeviceTokenToResponse(token))
}
return result
}
// DeviceTokenToAdminResponse 将DeviceToken转换为管理端响应含 registration_id
func DeviceTokenToAdminResponse(token *model.DeviceToken) *AdminDeviceTokenResponse {
if token == nil {
return nil
}
resp := &AdminDeviceTokenResponse{
ID: token.ID,
UserID: token.UserID,
DeviceID: token.DeviceID,
DeviceType: string(token.DeviceType),
PushToken: token.PushToken,
IsActive: token.IsActive,
DeviceName: token.DeviceName,
CreatedAt: token.CreatedAt,
UpdatedAt: token.UpdatedAt,
}
if token.LastUsedAt != nil {
resp.LastUsedAt = *token.LastUsedAt
}
return resp
}
// DeviceTokensToAdminResponse 将DeviceToken列表转换为管理端响应列表
func DeviceTokensToAdminResponse(tokens []*model.DeviceToken) []*AdminDeviceTokenResponse {
result := make([]*AdminDeviceTokenResponse, 0, len(tokens))
for _, token := range tokens {
result = append(result, DeviceTokenToAdminResponse(token))
}
return result
}