diff --git a/build.gradle b/build.gradle index d3759fe..50885f4 100644 --- a/build.gradle +++ b/build.gradle @@ -24,6 +24,7 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-freemarker' implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.boot:spring-boot-starter-data-redis' + implementation 'org.springframework.boot:spring-boot-starter-cache' implementation 'com.google.code.gson:gson' implementation 'com.h2database:h2' compileOnly 'org.projectlombok:lombok' diff --git a/src/main/java/com/redis/cluster/config/RedisCacheConfig.java b/src/main/java/com/redis/cluster/config/RedisCacheConfig.java index 1c11297..34440f3 100644 --- a/src/main/java/com/redis/cluster/config/RedisCacheConfig.java +++ b/src/main/java/com/redis/cluster/config/RedisCacheConfig.java @@ -8,6 +8,9 @@ import org.springframework.data.redis.cache.RedisCacheConfiguration; import org.springframework.data.redis.cache.RedisCacheManager; import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.listener.RedisMessageListenerContainer; +import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; import org.springframework.data.redis.serializer.RedisSerializationContext; import org.springframework.data.redis.serializer.StringRedisSerializer; @@ -36,4 +39,20 @@ public RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory) return RedisCacheManager.RedisCacheManagerBuilder.fromConnectionFactory(connectionFactory).cacheDefaults(configuration) .withInitialCacheConfigurations(cacheConfigurations).build(); } + + @Bean + public RedisMessageListenerContainer RedisMessageListener(RedisConnectionFactory connectionFactory) { + RedisMessageListenerContainer container = new RedisMessageListenerContainer(); + container.setConnectionFactory(connectionFactory); + return container; + } + + @Bean + public RedisTemplate redisTemplateForObject(RedisConnectionFactory connectionFactory) { + RedisTemplate redisTemplate = new RedisTemplate<>(); + redisTemplate.setConnectionFactory(connectionFactory); + redisTemplate.setKeySerializer(new StringRedisSerializer()); + redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer<>(String.class)); + return redisTemplate; + } } \ No newline at end of file diff --git a/src/main/java/com/redis/cluster/controller/PubSubController.java b/src/main/java/com/redis/cluster/controller/PubSubController.java new file mode 100644 index 0000000..bfd16b0 --- /dev/null +++ b/src/main/java/com/redis/cluster/controller/PubSubController.java @@ -0,0 +1,63 @@ +package com.redis.cluster.controller; + +import com.redis.cluster.pubsub.RedisPublisher; +import com.redis.cluster.pubsub.RedisSubscriber; +import com.redis.cluster.pubsub.RoomMessage; +import lombok.RequiredArgsConstructor; +import org.springframework.data.redis.listener.ChannelTopic; +import org.springframework.data.redis.listener.RedisMessageListenerContainer; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.PostConstruct; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +@RequiredArgsConstructor +@RequestMapping("/pubsub") +@RestController +public class PubSubController { + // topic에 발행되는 액션을 처리할 Listner + private final RedisMessageListenerContainer redisMessageListener; + // 발행자 + private final RedisPublisher redisPublisher; + // 구독자 + private final RedisSubscriber redisSubscriber; + // 특정 topic에 메시지를 발송할 수 있도록 topic정보를 Map에 저장 + private Map channels; + + @PostConstruct + public void init() { + // 실행될때 topic정보를 담을 Map을 초기화 + channels = new HashMap<>(); + } + + // 유효한 Topic 리스트 반환 + @GetMapping("/room") + public Set findAllRoom() { + return channels.keySet(); + } + + // Topic 생성하여 Listener에 등록후 Topic Map에 저장 + @PutMapping("/room/{roomId}") + public void createRoom(@PathVariable String roomId) { + ChannelTopic channel = new ChannelTopic(roomId); + redisMessageListener.addMessageListener(redisSubscriber, channel); + channels.put(roomId, channel); + } + + // 특정 Topic에 메시지 발송 + @PostMapping("/room/{roomId}") + public void pushMessage(@PathVariable String roomId, @RequestParam String name, @RequestParam String message) { + ChannelTopic channel = channels.get(roomId); + redisPublisher.publish(channel, RoomMessage.builder().name(name).roomId(roomId).message(message).build()); + } + + // 특정 Topic 삭제 후 Listener 해제 + @DeleteMapping("/room/{roomId}") + public void deleteRoom(@PathVariable String roomId) { + ChannelTopic channel = channels.get(roomId); + redisMessageListener.removeMessageListener(redisSubscriber, channel); + channels.remove(roomId); + } +} \ No newline at end of file diff --git a/src/main/java/com/redis/cluster/controller/RedisController.java b/src/main/java/com/redis/cluster/controller/RedisController.java index acafc8a..c564f58 100644 --- a/src/main/java/com/redis/cluster/controller/RedisController.java +++ b/src/main/java/com/redis/cluster/controller/RedisController.java @@ -23,21 +23,18 @@ public User findOne(@PathVariable long msrl) { } @PostMapping("/user") - @ResponseBody public User postUser(@RequestBody User user) { return userJpaRepo.save(user); } @CachePut(value = CacheKey.USER, key = "#user.msrl") @PutMapping("/user") - @ResponseBody public User putUser(@RequestBody User user) { return userJpaRepo.save(user); } @CacheEvict(value = CacheKey.USER, key = "#msrl") @DeleteMapping("/user/{msrl}") - @ResponseBody public boolean deleteUser(@PathVariable long msrl) { userJpaRepo.deleteById(msrl); return true; diff --git a/src/main/java/com/redis/cluster/pubsub/RedisPublisher.java b/src/main/java/com/redis/cluster/pubsub/RedisPublisher.java new file mode 100644 index 0000000..cf59994 --- /dev/null +++ b/src/main/java/com/redis/cluster/pubsub/RedisPublisher.java @@ -0,0 +1,17 @@ +package com.redis.cluster.pubsub; + +import lombok.RequiredArgsConstructor; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.listener.ChannelTopic; +import org.springframework.stereotype.Service; + +@RequiredArgsConstructor +@Service +public class RedisPublisher { + + private final RedisTemplate redisTemplate; + + public void publish(ChannelTopic topic, RoomMessage message) { + redisTemplate.convertAndSend(topic.getTopic(), message); + } +} diff --git a/src/main/java/com/redis/cluster/pubsub/RedisSubscriber.java b/src/main/java/com/redis/cluster/pubsub/RedisSubscriber.java new file mode 100644 index 0000000..12954c4 --- /dev/null +++ b/src/main/java/com/redis/cluster/pubsub/RedisSubscriber.java @@ -0,0 +1,29 @@ +package com.redis.cluster.pubsub; + +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.redis.connection.Message; +import org.springframework.data.redis.connection.MessageListener; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Service; + +@Slf4j +@RequiredArgsConstructor +@Service +public class RedisSubscriber implements MessageListener { + + private final ObjectMapper objectMapper; + private final RedisTemplate redisTemplate; + + @Override + public void onMessage(Message message, byte[] pattern) { + try { + String body = (String) redisTemplate.getStringSerializer().deserialize(message.getBody()); + RoomMessage roomMessage = objectMapper.readValue(body, RoomMessage.class); + log.info("Room - Message : {}", roomMessage.toString()); + } catch (Exception e) { + log.error(e.getMessage()); + } + } +} diff --git a/src/main/java/com/redis/cluster/pubsub/RoomMessage.java b/src/main/java/com/redis/cluster/pubsub/RoomMessage.java new file mode 100644 index 0000000..d77ef1e --- /dev/null +++ b/src/main/java/com/redis/cluster/pubsub/RoomMessage.java @@ -0,0 +1,17 @@ +package com.redis.cluster.pubsub; + +import lombok.*; + +import java.io.Serializable; + +@Getter +@Builder +@NoArgsConstructor +@AllArgsConstructor +@ToString +public class RoomMessage implements Serializable { + private static final long serialVersionUID = 2082503192322391880L; + private String roomId; + private String name; + private String message; +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 8fd0652..7917e7c 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -3,7 +3,7 @@ server: logging: level: - root: warn + root: info com.rest.api: debug spring: @@ -28,4 +28,4 @@ spring: - 15.164.98.87:6401 - 15.164.98.87:6402 max-redirects: 3 - password: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX + password: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX diff --git a/src/test/java/com/redis/cluster/RedisClusterTest.java b/src/test/java/com/redis/cluster/RedisClusterTest.java index 215e90f..c9cea9d 100644 --- a/src/test/java/com/redis/cluster/RedisClusterTest.java +++ b/src/test/java/com/redis/cluster/RedisClusterTest.java @@ -138,20 +138,29 @@ public void opsSortedSet() { public void opsGeo() { GeoOperations geoOps = redisTemplate.opsForGeo(); String[] cities = {"서울", "부산"}; - String[] gu = {"강남구", "서초구", "관악구", "동작구", "마포구", "사하구", "해운대구", "영도구", "동래구", "수영구"}; + String[][] gu = {{"강남구", "서초구", "관악구", "동작구", "마포구"}, {"사하구", "해운대구", "영도구", "동래구", "수영구"}}; + Point[][] pointGu = {{new Point(10, -10), new Point(11, -20), new Point(13, 10), new Point(14, 30), new Point(15, 40)}, {new Point(-100, 10), new Point(-110, 20), new Point(-130, 80), new Point(-140, 60), new Point(-150, 30)}}; String cacheKey = "valueGeo"; + + // previous key delete + redisTemplate.delete(cacheKey); + for (int x = 0; x < cities.length; x++) { - for (int y = 0; y < gu.length / 2; y++) { - geoOps.add(cacheKey, new Point(x, y), gu[x * y]); + for (int y = 0; y < 5; y++) { + geoOps.add(cacheKey, pointGu[x][y], gu[x][y]); } } + log.info("##### opsGeo #####"); Distance distance = geoOps.distance(cacheKey, "강남구", "동작구"); assertNotNull(distance); + assertEquals(4469610.0767, distance.getValue(), 4); log.info("Distance : {}", distance.getValue()); List position = geoOps.position(cacheKey, "동작구"); assertNotNull(position); for (Point point : position) { + assertEquals(14.000001847743988d, point.getX(), 4); + assertEquals(30.000000249977013d, point.getY(), 4); log.info("Position : {} x {}", point.getX(), point.getY()); } }