Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
143 changes: 132 additions & 11 deletions src/main/java/app/dearobjet/backend/domain/chat/entity/ChatMessage.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,13 @@

import java.time.LocalDateTime;

/**
* 채팅 메시지 엔티티
*/
@Entity
@Table(name = "chat_messages")
@Table(name = "chat_messages", indexes = {
@Index(name = "idx_room_created", columnList = "room_id, created_at DESC")
})
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
Expand All @@ -19,23 +24,139 @@ public class ChatMessage {
@Column(name = "chat_messages_id")
private Long id;

@Column(name = "message_type")
private String messageType; // TEXT, IMAGE, FILE
/**
* 채팅방 외부 식별자 (UUID)
*/
@Column(name = "room_id", nullable = false, length = 36)
private String roomId;

@Column(columnDefinition = "TEXT")
/**
* 메시지 타입 (TEXT, IMAGE, FILE, SYSTEM)
*/
@Enumerated(EnumType.STRING)
@Column(name = "message_type", nullable = false, length = 20)
private MessageType messageType;

/**
* 메시지 내용
* - TEXT: 텍스트 메시지
* - IMAGE: 이미지 URL
* - FILE: 파일 URL
* - SYSTEM: 시스템 메시지 ("OOO님이 입장하였습니다" 등)
*/
@Column(name = "content", columnDefinition = "TEXT")
private String content;

@Column(name = "created_at")
/**
* 메시지 생성 시간
* 정렬 및 읽음 처리에 사용
*/
@Column(name = "created_at", nullable = false)
private LocalDateTime createdAt;

@Column(name = "is_read")
private Boolean isRead;

/**
* 채팅방 (FK)
* 주로 참조 무결성을 위해 유지
* 조회 시에는 roomId 사용
*/
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "chat_room_id", nullable = false)
private ChatRoom chatRoom;

/**
* 메시지 발신자
*/
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id")
private User user;
}
@JoinColumn(name = "user_id", nullable = false)
private User sender;

// ====================================================================
// 팩토리 메서드
// ====================================================================

/**
* 메시지 생성
*
* @param roomId 채팅방 UUID
* @param sender 발신자
* @param content 메시지 내용
* @param type 메시지 타입
* @return 새로 생성된 메시지
*/
public static ChatMessage create(String roomId, User sender, String content, MessageType type) {
return ChatMessage.builder()
.roomId(roomId)
.sender(sender)
.content(content)
.messageType(type)
.createdAt(LocalDateTime.now())
.build();
}

/**
* 텍스트 메시지 생성 (편의 메서드)
*/
public static ChatMessage createTextMessage(String roomId, User sender, String content) {
return create(roomId, sender, content, MessageType.TEXT);
}

/**
* 시스템 메시지 생성 (편의 메서드)
* 예: "홍길동님이 입장하였습니다"
*/
public static ChatMessage createSystemMessage(String roomId, String content) {
return ChatMessage.builder()
.roomId(roomId)
.sender(null) // 시스템 메시지는 발신자 없음
.content(content)
.messageType(MessageType.SYSTEM)
.createdAt(LocalDateTime.now())
.build();
}

// ====================================================================
// JPA Lifecycle Callbacks
// ====================================================================

/**
* 엔티티 저장 전 호출
* createdAt 자동 설정
*/
@PrePersist
protected void onCreate() {
if (createdAt == null) {
createdAt = LocalDateTime.now();
}
if (messageType == null) {
messageType = MessageType.TEXT;
}
}

// ====================================================================
// 비즈니스 메서드
// ====================================================================

/**
* 특정 사용자가 이 메시지를 읽었는지 확인
*
* @param participant 확인할 참여자
* @return true - 읽음, false - 안 읽음
*/
public boolean isReadBy(ChatParticipant participant) {
return participant.hasRead(this.createdAt);
}

/**
* 시스템 메시지인지 확인
*/
public boolean isSystemMessage() {
return this.messageType == MessageType.SYSTEM;
}

/**
* 텍스트 메시지인지 확인
*/
public boolean isTextMessage() {
return this.messageType == MessageType.TEXT;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,23 @@
import jakarta.persistence.*;
import lombok.*;

import java.time.LocalDateTime;

/**
* 채팅방 참여자 엔티티
*/
@Entity
@Table(name = "chat_participants")
@Table(name = "chat_participants",
uniqueConstraints = {
@UniqueConstraint(
name = "uk_room_user",
columnNames = {"chat_room_id", "user_id"}
)
},
indexes = {
@Index(name = "idx_user_room", columnList = "user_id, chat_room_id")
}
)
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
Expand All @@ -17,20 +32,118 @@ public class ChatParticipant extends BaseTimeEntity {
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@Column(name = "field")
private String field;

@Column(name = "field2")
private String field2;

/**
* 참여 중인 사용자
*/
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id")
@JoinColumn(name = "user_id", nullable = false)
private User user;

/**
* 참여 중인 채팅방
*/
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "chat_room_id", nullable = false)
private ChatRoom chatRoom;

@Column(name = "unread_count")
/**
* 읽지 않은 메시지 수
*/
@Column(name = "unread_count", nullable = false)
private Integer unreadCount;
}

/**
* 채팅방 참여 시점
*/
@Column(name = "joined_at", nullable = false)
private LocalDateTime joinedAt;

/**
* 마지막으로 메시지를 읽은 시점
*
* 읽음 표시 구현 방식:
* 1. 사용자가 채팅방 진입 시 현재 시간으로 업데이트
* 2. 메시지의 createdAt과 비교하여 읽음 여부 판단
* 3. message.createdAt <= participant.lastReadAt → 읽음
*/
@Column(name = "last_read_at")
private LocalDateTime lastReadAt;

// ====================================================================
// 비즈니스 메서드
// ====================================================================

/**
* 읽지 않은 메시지 수 증가
* 새 메시지가 도착했을 때 호출
*/
public void incrementUnreadCount() {
if (this.unreadCount == null) {
this.unreadCount = 0;
}
this.unreadCount++;
}

/**
* 메시지 읽음 처리
* 채팅방 진입 시 호출
*/
public void markAsRead() {
this.lastReadAt = LocalDateTime.now();
this.unreadCount = 0;
}

/**
* 읽지 않은 메시지 수 업데이트
* 배치 작업 등에서 사용
*
* @param count 새로운 읽지 않은 메시지 수
*/
public void updateUnreadCount(int count) {
this.unreadCount = Math.max(0, count); // 음수 방지
}

/**
* 특정 시점 이후의 메시지를 읽음 처리
*
* @param readUntil 읽음 처리할 시점
*/
public void markAsReadUntil(LocalDateTime readUntil) {
this.lastReadAt = readUntil;
}

// ====================================================================
// JPA Lifecycle Callbacks
// ====================================================================

/**
* 엔티티 저장 전 호출
* joinedAt 자동 설정
*/
@PrePersist
protected void onCreate() {
if (joinedAt == null) {
joinedAt = LocalDateTime.now();
}
if (unreadCount == null) {
unreadCount = 0;
}
}

// ====================================================================
// 편의 메서드
// ====================================================================

/**
* 특정 메시지를 읽었는지 확인
*
* @param messageCreatedAt 메시지 생성 시간
* @return true - 읽음, false - 안 읽음
*/
public boolean hasRead(LocalDateTime messageCreatedAt) {
if (lastReadAt == null) {
return false;
}
return !messageCreatedAt.isAfter(lastReadAt);
}
}
Loading