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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,6 @@ logs/*

### application*.properties ###
src/main/resources/*.properties

### p6spy log ###
spy.log
3 changes: 3 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,9 @@ dependencies {
/* visibility */
implementation 'org.springframework.boot:spring-boot-starter-actuator:3.2.4'
runtimeOnly 'io.micrometer:micrometer-registry-prometheus'

implementation 'com.github.gavlyukovskiy:p6spy-spring-boot-starter:1.9.2'

}

tasks.register('copyPrivate', Copy) {
Expand Down
18 changes: 18 additions & 0 deletions src/main/java/one/colla/global/config/log/P6SpyConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package one.colla.global.config.log;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class P6SpyConfig {

@Bean
public P6SpyEventListener p6SpyCustomEventListener() {
return new P6SpyEventListener();
}

@Bean
public P6SpyFormatter p6SpyCustomFormatter() {
return new P6SpyFormatter();
}
}
15 changes: 15 additions & 0 deletions src/main/java/one/colla/global/config/log/P6SpyEventListener.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package one.colla.global.config.log;

import java.sql.SQLException;

import com.p6spy.engine.common.ConnectionInformation;
import com.p6spy.engine.event.JdbcEventListener;
import com.p6spy.engine.spy.P6SpyOptions;

public class P6SpyEventListener extends JdbcEventListener {

@Override
public void onAfterGetConnection(ConnectionInformation connectionInformation, SQLException ex) {
P6SpyOptions.getActiveInstance().setLogMessageFormat(P6SpyFormatter.class.getName());
}
}
71 changes: 71 additions & 0 deletions src/main/java/one/colla/global/config/log/P6SpyFormatter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package one.colla.global.config.log;

import java.util.Locale;
import java.util.Set;

import org.hibernate.engine.jdbc.internal.FormatStyle;

import com.p6spy.engine.logging.Category;
import com.p6spy.engine.spy.appender.MessageFormattingStrategy;

public class P6SpyFormatter implements MessageFormattingStrategy {

private static final String NEW_LINE = System.lineSeparator();
private static final String TAB = "\t";
private static final Set<String> DDL_KEYWORDS = Set.of("create", "alter", "drop", "comment");
private static final String CONNECTION_ID_FORMAT = "Connection ID: %s";
private static final String SEPARATOR = "-".repeat(200);

@Override
public String formatMessage(int connectionId, String now, long elapsed, String category, String prepared,
String sql, String url) {
if (sql.trim().isEmpty()) {
return formatByCommand(category, connectionId);
}
return formatBySql(sql, category) + formatAdditionalInfo(elapsed, connectionId);
}

private static String formatByCommand(String category, int connectionId) {
return String.format("%s | Category: %s", String.format(CONNECTION_ID_FORMAT, connectionId), category);
}

private String formatBySql(String sql, String category) {
String formattedSql = isStatementDdl(sql, category)
? formatDdl(sql)
: formatDml(sql);

return NEW_LINE + removeTimeZoneOffset(formattedSql);
}

private String formatDdl(String sql) {
return NEW_LINE + "Execute DDL : " + FormatStyle.DDL.getFormatter().format(sql);
}

private String formatDml(String sql) {
return NEW_LINE + "Execute DML : " + FormatStyle.BASIC.getFormatter().format(sql);
}

private String formatAdditionalInfo(long elapsed, int connectionId) {
return String.join(
NEW_LINE,
"",
"",
TAB + String.format(CONNECTION_ID_FORMAT, connectionId),
TAB + String.format("Execution Time: %s ms", elapsed),
"",
SEPARATOR
);
}

private boolean isStatementDdl(String sql, String category) {
return Category.STATEMENT.getName().equals(category) && isDdl(sql.trim().toLowerCase(Locale.ROOT));
}

private boolean isDdl(String lowerSql) {
return DDL_KEYWORDS.stream().anyMatch(lowerSql::startsWith);
}

private String removeTimeZoneOffset(String sql) {
return sql.replace("+0900", "");
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package one.colla.teamspace.application;

import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;

import org.apache.commons.lang3.tuple.Pair;
import org.springframework.context.ApplicationEventPublisher;
Expand Down Expand Up @@ -298,6 +300,15 @@ private Pair<InviteCode, UserTeamspace> generateAndSaveInviteCodeByTeamspaceId(C
return Pair.of(inviteCode, userTeamspace);
}

@Transactional(readOnly = true)
public Map<Long, Long> countParticipantsByTeamspaceIds(List<Long> teamspaceIds) {
return teamspaceRepository.countParticipantsByTeamspaceIds(teamspaceIds).stream()
.collect(Collectors.toMap(
arr -> (Long)arr[0], // 팀스페이스 ID
arr -> (Long)arr[1] // 참여자 수
));
}

/**
* 현재 사용자가 특정 팀 스페이스의 참가자인지 확인하고, 해당 팀 스페이스에 대한 사용자-팀 매핑 엔티티를 반환합니다.
*
Expand Down
10 changes: 10 additions & 0 deletions src/main/java/one/colla/teamspace/domain/TeamspaceRepository.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
package one.colla.teamspace.domain;

import java.util.List;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;

public interface TeamspaceRepository extends JpaRepository<Teamspace, Long> {
@Query("SELECT t.id, COUNT(ut) "
+ "FROM Teamspace t "
+ "JOIN t.userTeamspaces ut "
+ "WHERE t.id IN :teamspaceIds "
+ "GROUP BY t.id")
List<Object[]> countParticipantsByTeamspaceIds(@Param("teamspaceIds") List<Long> teamspaceIds);
}
54 changes: 30 additions & 24 deletions src/main/java/one/colla/user/application/UserService.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package one.colla.user.application;

import java.util.List;
import java.util.Map;
import java.util.Optional;

import org.springframework.stereotype.Service;
Expand All @@ -16,7 +17,6 @@
import one.colla.infra.redis.lastseen.LastSeenTeamspace;
import one.colla.infra.redis.lastseen.LastSeenTeamspaceService;
import one.colla.teamspace.application.TeamspaceService;
import one.colla.teamspace.domain.Teamspace;
import one.colla.teamspace.domain.UserTeamspace;
import one.colla.user.application.dto.request.LastSeenUpdateRequest;
import one.colla.user.application.dto.request.UpdateUserSettingRequest;
Expand Down Expand Up @@ -45,44 +45,50 @@ public Optional<User> getUserById(Long id) {

@Transactional(readOnly = true)
public UserStatusResponse getUserStatus(CustomUserDetails userDetails) {
final User user = userRepository.findById(userDetails.getUserId())
final User user = userRepository.findByIdWithTeamspaces(userDetails.getUserId())
.orElseThrow(() -> new CommonException(ExceptionCode.NOT_FOUND_USER));

Long lastSeenTeamspaceId = lastSeenTeamspaceService.findByUserId(user.getId())
.map(LastSeenTeamspace::getTeamspaceId)
.orElse(null);

ProfileDto profile = ProfileDto.of(user.getId(), user, lastSeenTeamspaceId);

List<Long> teamspaceIds = user.getUserTeamspaces().stream()
.map(ut -> ut.getTeamspace().getId())
.toList();
Map<Long, Long> participantCountMap = teamspaceService.countParticipantsByTeamspaceIds(teamspaceIds);
List<ParticipatedTeamspaceDto> participatedTeamspaces = user.getUserTeamspaces().stream()
.map(ut -> ParticipatedTeamspaceDto.of(
ut.getTeamspace().getId(),
.map(ut -> {
Long teamspaceId = ut.getTeamspace().getId();
return ParticipatedTeamspaceDto.of(
teamspaceId,
ut,
getNumOfTeamspaceParticipants(ut),
calculateUnreadMessageCount(ut.getUser().getId(), ut.getTeamspace())
)
)
participantCountMap.getOrDefault(teamspaceId, 0L).intValue(),
1 // 읽지 않은 메세지 임시 처리 (향후 최적화 가능)
);
})
.toList();

log.info("사용자 관련 정보 조회 - 사용자 Id: {}", userDetails.getUserId());
return UserStatusResponse.of(profile, participatedTeamspaces);
}

private int calculateUnreadMessageCount(Long userId, Teamspace teamspace) {
return teamspace.getChatChannels().stream()
.mapToInt(chatChannel -> userChatChannelRepository.findByUserIdAndChatChannelId(userId, chatChannel.getId())
.map(userChatChannel -> {
Long lastReadMessageId = userChatChannel.getLastReadMessageId();
if (lastReadMessageId == null) {
return chatChannelMessageRepository.countByChatChannel(chatChannel);
} else {
return chatChannelMessageRepository.countByChatChannelAndIdGreaterThan(chatChannel,
lastReadMessageId);
}
})
.orElse(0))
.sum();
}
// TODO: API 분리 필요
// private int calculateUnreadMessageCount(Long userId, Teamspace teamspace) {
// return teamspace.getChatChannels().stream()
// .mapToInt(chatChannel -> userChatChannelRepository.findByUserIdAndChatChannelId(userId, chatChannel.getId())
// .map(userChatChannel -> {
// Long lastReadMessageId = userChatChannel.getLastReadMessageId();
// if (lastReadMessageId == null) {
// return chatChannelMessageRepository.countByChatChannel(chatChannel);
// } else {
// return chatChannelMessageRepository.countByChatChannelAndIdGreaterThan(chatChannel,
// lastReadMessageId);
// }
// })
// .orElse(0))
// .sum();
// }

@Transactional
public void updateLastSeenTeamspace(CustomUserDetails userDetails, LastSeenUpdateRequest request) {
Expand Down
8 changes: 8 additions & 0 deletions src/main/java/one/colla/user/domain/UserRepository.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,17 @@
import java.util.Optional;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;

import one.colla.user.domain.vo.Email;

public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByEmail(Email email);

@Query("SELECT DISTINCT u FROM User u "
+ "LEFT JOIN FETCH u.userTeamspaces ut "
+ "LEFT JOIN FETCH ut.teamspace t "
+ "WHERE u.id = :userId")
Optional<User> findByIdWithTeamspaces(@Param("userId") Long userId);
}
6 changes: 5 additions & 1 deletion src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -68,14 +68,18 @@ spring:
websocket:
allowed-origins: http://localhost:8080, http://localhost:3000, ${BASE_URL}


springdoc:
default-consumes-media-type: application/json;charset=UTF-8
default-produces-media-type: application/json;charset=UTF-8
swagger-ui:
url: /docs/open-api-3.0.1.yaml
path: /swagger

decorator:
datasource:
p6spy:
enable-logging: true

jwt:
secret-key:
access-token: collaAccessAccessAccessTokenSecretKeyForCollaSystem
Expand Down