Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: 최근 접속 시간 조회 기능 구현 #267

Merged
merged 10 commits into from
Dec 11, 2024
2 changes: 1 addition & 1 deletion .github/workflows/aws-cicd-dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ on:
push:
branches:
- develop
- feat/LA-20_3
- feat/LA-27-2

env:
REGISTRY: "docker.io"
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/aws-cicd-prod.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ on:
push:
branches:
- main
# - feat/LA-27-2

env:
REGISTRY: "docker.io"
Expand Down
6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -58,4 +58,8 @@ credentials.json

layer-api/src/main/resources/tokens/StoredCredential
layer-batch/src/main/resources/application-secret.properties
layer-admin/src/main/resources/application-secret.properties
layer-admin/src/main/resources/application-secret.properties
layer-admin/src/main/resources/application.yml
layer-admin/src/main/resources/data.sql


5 changes: 5 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ project(":layer-api") {

implementation 'org.springframework.boot:spring-boot-starter-actuator'
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'org.springframework.boot:spring-boot-starter-web'

//== jwt ==//
implementation 'io.jsonwebtoken:jjwt-api:0.12.5'
Expand Down Expand Up @@ -201,6 +202,7 @@ project(":layer-admin") {

dependencies {
implementation project(path: ':layer-domain')
implementation project(path: ':layer-common')

implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
Expand All @@ -211,6 +213,9 @@ project(":layer-admin") {

// Security
implementation 'org.springframework.boot:spring-boot-starter-security'

// redis
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package org.layer.common.exception;

public class AdminException extends BaseCustomException {
public AdminException(ExceptionType exceptionType) {
super(exceptionType);
}
}
68 changes: 68 additions & 0 deletions layer-admin/src/main/java/org/layer/config/RedisConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package org.layer.config;

import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.jsontype.BasicPolymorphicTypeValidator;
import com.fasterxml.jackson.databind.jsontype.PolymorphicTypeValidator;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.repository.configuration.EnableRedisRepositories;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;


@Configuration
@EnableRedisRepositories
public class RedisConfig {
@Value("${spring.data.redis.host}")
private String host;

@Value("${spring.data.redis.port}")
private int port;

@Value("${spring.data.redis.password}")
private String password;

// prod에서 최근 서비스 이용 시점 기록 - 1번 데이터베이스
@Bean
public RedisConnectionFactory redisConnectionFactory() {
RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration();
redisStandaloneConfiguration.setHostName(host);
redisStandaloneConfiguration.setPort(port);
redisStandaloneConfiguration.setDatabase(1); // 1번 데이터베이스

return new LettuceConnectionFactory(redisStandaloneConfiguration);

}

@Bean
public RedisTemplate<String, Object> redisTemplate() {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory());

PolymorphicTypeValidator typeValidator = BasicPolymorphicTypeValidator
.builder()
.allowIfSubType(Object.class)
.build();

ObjectMapper objectMapper = new ObjectMapper()
.findAndRegisterModules()
.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
.configure(DeserializationFeature.FAIL_ON_IGNORED_PROPERTIES, false)
.activateDefaultTyping(typeValidator, ObjectMapper.DefaultTyping.NON_FINAL)
.registerModule(new JavaTimeModule());

template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(new GenericJackson2JsonRedisSerializer(objectMapper));

return template;
}

}
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
package org.layer.member.controller.dto;

import java.time.LocalDateTime;

import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;

import java.time.LocalDateTime;

@Schema(name = "GetMemberActivityResponse", description = "회원 활동 Dto")
public record GetMemberActivityResponse(
@NotNull
@Schema(description = "회원 이름", example = "홍길동")
String name,
@NotNull
@Schema(description = "최근 활동 날짜", example = "2024-11-30T16:21:47.031Z")
@Schema(description = "최근 활동 날짜, 최근 6개월 동안 접속 없을 시 null", example = "2024-11-30T16:21:47.031Z")
mikekks marked this conversation as resolved.
Show resolved Hide resolved
LocalDateTime recentActivityDate,
@NotNull
@Schema(description = "소속된 스페이스 수", example = "7")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
package org.layer.member.service;

import java.util.List;
import lombok.RequiredArgsConstructor;

import org.layer.common.dto.RecentActivityDto;
import org.layer.domain.answer.repository.AdminAnswerRepository;
import org.layer.domain.member.entity.Member;
import org.layer.domain.member.repository.AdminMemberRepository;
Expand All @@ -11,10 +12,11 @@
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import lombok.RequiredArgsConstructor;
import java.util.List;

@Service
@RequiredArgsConstructor
Expand All @@ -23,6 +25,7 @@ public class AdminMemberService {
private final AdminMemberRepository adminMemberRepository;
private final AdminMemberSpaceRelationRepository adminMemberSpaceRelationRepository;
private final AdminAnswerRepository adminAnswerRepository;
private final RedisTemplate<String, Object> redisTemplate;

@Value("${admin.password}")
private String password;
Expand All @@ -43,8 +46,12 @@ public GetMembersActivitiesResponse getMemberActivities(String password, int pag
Long spaceCount = adminMemberSpaceRelationRepository.countAllByMemberId(member.getId());
Long retrospectAnswerCount = adminAnswerRepository.countAllByMemberId(member.getId());

return new GetMemberActivityResponse(member.getName(), null, spaceCount, retrospectAnswerCount,
member.getCreatedAt(), member.getSocialType().name());
RecentActivityDto recentActivityDto = (RecentActivityDto)redisTemplate.opsForValue()
.get(Long.toString(member.getId()));

return new GetMemberActivityResponse(member.getName(),
recentActivityDto == null ? null : recentActivityDto.getRecentActivityDate(),
spaceCount, retrospectAnswerCount, member.getCreatedAt(), member.getSocialType().name());
}).toList();

return new GetMembersActivitiesResponse(responses);
Expand Down
5 changes: 5 additions & 0 deletions layer-admin/src/main/resources/application-dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ spring:
show_sql: true
open-in-view: false
database: mysql
data:
redis:
host: ${DEV_REDIS_HOST}
port: ${DEV_REDIS_PORT}
password: ${DEV_REDIS_PASSWORD}

admin:
password: ${ADMIN_PASSWORD}
34 changes: 34 additions & 0 deletions layer-admin/src/main/resources/application-local.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
server:
port: 3000

spring:
config:
import: application-secret.properties
datasource:
driver-class-name: org.h2.Driver
url: jdbc:h2:mem:layer-local-db;DATABASE_TO_UPPER=FALSE;mode=mysql # H2 접속 정보 (전부 소문자로 지정)
username: sa
password:
h2:
console:
enabled: true
path: /h2-console
jpa:
database-platform: org.hibernate.dialect.H2Dialect
hibernate:
ddl-auto: create
properties:
hibernate:
format_sql: true
show_sql: true
open-in-view: false
defer-datasource-initialization: true

data:
redis:
host: localhost
port: 6379
password:

admin:
password: ${ADMIN_PASSWORD}
6 changes: 6 additions & 0 deletions layer-admin/src/main/resources/application-prod.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,11 @@ spring:
open-in-view: false
database: mysql

data:
redis:
host: ${DEV_REDIS_HOST}
port: ${DEV_REDIS_PORT}
password: ${DEV_REDIS_PASSWORD}

admin:
password: ${ADMIN_PASSWORD}
8 changes: 8 additions & 0 deletions layer-api/infra/development/Dockerfile-redis
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Base image
FROM redis:latest

# Copy custom Redis configuration
COPY redis.conf /usr/local/etc/redis/redis.conf

# Command to run Redis with the custom configuration
CMD ["redis-server", "/usr/local/etc/redis/redis.conf"]
17 changes: 16 additions & 1 deletion layer-api/infra/development/docker-compose.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,16 @@
services:
redis:
build:
context: .
dockerfile: Dockerfile-redis
ports:
- "6379:6379"
volumes:
- redis-data:/data # Persistent data storage
restart: always
networks:
- app-network

java-app:
image: docker.io/clean01/layer-server_layer-api:latest
container_name: layer-api
Expand Down Expand Up @@ -60,4 +72,7 @@ services:
- app-network

networks:
app-network:
app-network:

volumes:
redis-data:
2 changes: 1 addition & 1 deletion layer-api/infra/development/nginx.conf
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ http {
# api.layerapp.io에 대한 서버 블록
server {
listen 80;
server_name api.layerapp.io;
server_name stgapi.layerapp.io;

location / {
proxy_pass http://layer-api;
Expand Down
20 changes: 20 additions & 0 deletions layer-api/infra/development/redis.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Save the DB snapshot every 180 seconds if at least 1 key changes
save 180 1

# Specify the filename for the RDB file
dbfilename dump.rdb

# Directory where the RDB snapshot will be saved
dir /data

# Enable RDB snapshot logging (optional for debugging)
loglevel notice

# Disable AOF (if you want only RDB persistence)
appendonly no

# Compression for RDB files (enabled by default)
rdbcompression yes

# Checksum verification for RDB files (enabled by default)
rdbchecksum yes
8 changes: 8 additions & 0 deletions layer-api/infra/production/Dockerfile-redis
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Base image
FROM redis:latest

# Copy custom Redis configuration
COPY redis.conf /usr/local/etc/redis/redis.conf

# Command to run Redis with the custom configuration
CMD ["redis-server", "/usr/local/etc/redis/redis.conf"]
17 changes: 16 additions & 1 deletion layer-api/infra/production/docker-compose-blue.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,16 @@
services:
redis:
build:
context: .
dockerfile: Dockerfile-redis
ports:
- "6379:6379"
volumes:
- redis-data:/data # Persistent data storage
restart: always
networks:
- app-network

layer-api-blue:
image: docker.io/clean01/layer-server_layer-api:latest
container_name: layer-api-blue
Expand Down Expand Up @@ -48,4 +60,7 @@ services:
restart: always

networks:
app-network:
app-network:

volumes:
redis-data:
17 changes: 16 additions & 1 deletion layer-api/infra/production/docker-compose-green.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,16 @@
services:
redis:
build:
context: .
dockerfile: Dockerfile-redis
ports:
- "6379:6379"
volumes:
- redis-data:/data # Persistent data storage
restart: always
networks:
- app-network

layer-api-green:
image: docker.io/clean01/layer-server_layer-api:latest
container_name: layer-api-green
Expand Down Expand Up @@ -48,4 +60,7 @@ services:
restart: always

networks:
app-network:
app-network:

volumes:
redis-data:
Loading
Loading