Skip to content

Commit

Permalink
[Feature/326] 서버에서 블러 이미지 처리하도록 수정 (#431)
Browse files Browse the repository at this point in the history
* feat: 블러 이미지 등록 구현

* feat: 기존 유저들 blurImage 등록하는 API 구현

* feat: 코드 깨지는 것 수정

* feat: 오타 수정

* feat: id 필터
  • Loading branch information
injoon2019 authored Sep 19, 2024
1 parent ba39ab3 commit 2cfa9d0
Show file tree
Hide file tree
Showing 12 changed files with 188 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -79,4 +79,10 @@ class AdminController(
fun sendPushMessages(@RequestBody pushMessagesRequest: PushMessageRequest) {
adminFacade.sendPushMessages(pushMessagesRequest)
}

@ApiOperation("블러 이미지 생성")
@PostMapping("/blur-image")
fun makeBlurImage() {
adminFacade.makeBlurImage()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ import com.nexters.bottles.api.user.facade.dto.InterestDto
import com.nexters.bottles.api.user.facade.dto.RegionDto
import com.nexters.bottles.app.admin.service.AdminService
import com.nexters.bottles.app.bottle.domain.enum.BottleStatus
import com.nexters.bottles.app.common.component.AmazonS3FileService
import com.nexters.bottles.app.common.component.ImageProcessor
import com.nexters.bottles.app.common.component.ImageUploader
import com.nexters.bottles.app.config.CacheType.Name.PING_PONG_BOTTLE_LIST
import com.nexters.bottles.app.notification.component.FcmClient
import com.nexters.bottles.app.notification.component.dto.FcmNotification
Expand All @@ -17,11 +20,16 @@ import com.nexters.bottles.app.user.domain.UserProfile
import com.nexters.bottles.app.user.domain.UserProfileSelect
import com.nexters.bottles.app.user.domain.enum.Gender
import com.nexters.bottles.app.user.domain.enum.SignUpType
import com.nexters.bottles.app.user.service.UserProfileService
import com.nexters.bottles.app.user.service.UserService
import mu.KotlinLogging
import org.springframework.cache.annotation.CacheEvict
import org.springframework.cache.annotation.Caching
import org.springframework.stereotype.Component
import org.springframework.web.multipart.MultipartFile
import java.time.LocalDate
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter

@Component
class AdminFacade(
Expand All @@ -30,6 +38,11 @@ class AdminFacade(
private val testJwtTokenProvider: TestJwtTokenProvider,
private val fcmClient: FcmClient,
private val fcmTokenService: FcmTokenService,
private val userService: UserService,
private val userProfileService: UserProfileService,
private val imageProcessor: ImageProcessor,
private val imageUploader: ImageUploader,
private val amazonS3FileService: AmazonS3FileService,
) {

private val log = KotlinLogging.logger { }
Expand Down Expand Up @@ -149,7 +162,27 @@ class AdminFacade(
}
}

fun makeBlurImage() {
userProfileService.findAllWithImage()
.filter { it.user.id > 150 }
.forEach {
val imageFile = amazonS3FileService.downloadAsMultipartFile(it.imageUrl!!.substringAfterLast("/"))
val path = makePathWithUserId(imageFile, it.user.id)
val blurredImageUrl = imageUploader.uploadWithBlur(imageFile, path);

userProfileService.addBlurImageUrl(it.id, blurredImageUrl.toString())
}
}

fun makePathWithUserId(
file: MultipartFile,
userId: Long
) = "" + userId + FILE_NAME_DELIMITER + LocalDateTime.now()
.format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")) + FILE_NAME_DELIMITER + file.originalFilename


companion object {
private const val FILE_NAME_DELIMITER = "_"
private const val mockMaleUserId = 1L
private const val mockFemaleUserId1 = 2L
private const val mockFemaleUserId2 = 9L
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import com.nexters.bottles.api.user.facade.dto.UserProfileResponse
import com.nexters.bottles.api.user.facade.dto.UserProfileStatus
import com.nexters.bottles.api.user.facade.dto.UserProfileStatusResponse
import com.nexters.bottles.app.common.component.FileService
import com.nexters.bottles.app.common.component.ImageUploader
import com.nexters.bottles.app.user.domain.User
import com.nexters.bottles.app.user.domain.UserProfile
import com.nexters.bottles.app.user.domain.UserProfileSelect
Expand All @@ -29,7 +30,8 @@ import java.time.format.DateTimeFormatter
class UserProfileFacade(
private val profileService: UserProfileService,
private val userService: UserService,
private val fileService: FileService
private val fileService: FileService,
private val imageUploader: ImageUploader,
) {

private val log = KotlinLogging.logger { }
Expand Down Expand Up @@ -123,11 +125,12 @@ class UserProfileFacade(
val me = userService.findByIdAndNotDeleted(userId)
val path = makePathWithUserId(file, me.id)
val originalImageUrl = fileService.upload(file, path)
val blurredImageUrl = imageUploader.uploadWithBlur(file, path);

profileService.uploadImageUrl(me, originalImageUrl.toString())
profileService.uploadImageUrl(me, originalImageUrl.toString(), blurredImageUrl.toString())
}

private fun makePathWithUserId(
fun makePathWithUserId(
file: MultipartFile,
userId: Long
) = "" + userId + FILE_NAME_DELIMITER + LocalDateTime.now()
Expand Down
1 change: 1 addition & 0 deletions api/src/main/resources/sql/ddl/table_query.sql
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ CREATE TABLE user_profile
profile_select JSON,
introduction JSON,
image_url VARCHAR(2048),
blurred_image_url VARCHAR(2048),
created_at DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP NOT NULL
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
package com.nexters.bottles.app.common.component

import com.amazonaws.services.s3.AmazonS3
import com.amazonaws.services.s3.model.GetObjectRequest
import com.amazonaws.services.s3.model.ObjectMetadata
import com.amazonaws.services.s3.model.PutObjectRequest
import org.springframework.beans.factory.annotation.Value
import org.springframework.stereotype.Component
import org.springframework.web.multipart.MultipartFile
import java.io.File
import java.io.*
import java.net.URL
import java.nio.file.Files
import java.nio.file.Paths

@Component
class AmazonS3FileService(
Expand Down Expand Up @@ -50,4 +52,48 @@ class AmazonS3FileService(
amazonS3.putObject(putObjectRequest)
return amazonS3.getUrl(bucket, key)
}

fun downloadAsMultipartFile(key: String): MultipartFile {
// S3에서 파일 가져오기
println("key: $key")
val s3Object = amazonS3.getObject(GetObjectRequest(bucket, key))
val inputStream = s3Object.objectContent

// 바이트 배열로 변환
val outputStream = ByteArrayOutputStream()
inputStream.use { input ->
outputStream.use { output ->
input.copyTo(output)
}
}

// S3 파일 이름 및 MIME 타입 설정
val fileName = key.substringAfterLast("/")
val contentType = Files.probeContentType(Paths.get(fileName)) ?: "application/octet-stream"

// CustomMultipartFile로 변환
return CustomMultipartFile(
outputStream.toByteArray(),
fileName,
contentType
)
}
}

class CustomMultipartFile(
private val content: ByteArray,
private val fileName: String,
private val contentType: String
) : MultipartFile {

override fun getName(): String = fileName
override fun getOriginalFilename(): String = fileName
override fun getContentType(): String = contentType
override fun isEmpty(): Boolean = content.isEmpty()
override fun getSize(): Long = content.size.toLong()
override fun getBytes(): ByteArray = content
override fun getInputStream(): InputStream = ByteArrayInputStream(content)
override fun transferTo(dest: File) {
FileOutputStream(dest).use { it.write(content) }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.nexters.bottles.app.common.component

import org.springframework.stereotype.Component
import java.awt.image.BufferedImage
import java.awt.image.ConvolveOp
import java.awt.image.Kernel
import java.io.File
import javax.imageio.ImageIO
import kotlin.math.sqrt

@Component
class ImageProcessor {

fun blurImage(file: File): BufferedImage {
val image = ImageIO.read(file)
val blurredImage = applyGaussianBlur(image)
return blurredImage
}

private fun applyGaussianBlur(image: BufferedImage): BufferedImage {
val matrix = FloatArray(1225) { 1 / 1225f }
val size = sqrt(matrix.size.toDouble()).toInt()
val kernel = Kernel(size, size, matrix)
val convolveOp = ConvolveOp(kernel, ConvolveOp.EDGE_ZERO_FILL, null)

val blurredImage = BufferedImage(image.width, image.height, image.type)
convolveOp.filter(image, blurredImage)
return blurredImage
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package com.nexters.bottles.app.common.component

import org.springframework.stereotype.Component
import org.springframework.web.multipart.MultipartFile
import java.io.File
import java.net.URL
import java.nio.file.Files
import java.nio.file.Paths
import java.time.LocalDateTime
import javax.imageio.ImageIO

@Component
class ImageUploader(
private val imageProcessor: ImageProcessor,
private val fileService: FileService
) {

fun upload(file: MultipartFile, path: String): URL {
return fileService.upload(file, path)
}

fun uploadWithBlur(file: MultipartFile, path: String): URL {
val uploadDir = Paths.get("uploads")
if (!Files.exists(uploadDir)) {
Files.createDirectories(uploadDir)
}

val originalFilePath = uploadDir.resolve("original_" + LocalDateTime.now() + file.originalFilename)
Files.copy(file.inputStream, originalFilePath)

val blurredImage = imageProcessor.blurImage(File(originalFilePath.toString()))

val blurredFilePath = uploadDir.resolve("blurred_" + LocalDateTime.now() + file.originalFilename)
ImageIO.write(blurredImage, "jpg", File(blurredFilePath.toString()))

val imageUrlBlur = fileService.upload(blurredFilePath.toString(), "blurred_${path}")

Files.deleteIfExists(originalFilePath)
Files.deleteIfExists(blurredFilePath)

return imageUrlBlur
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ class UserProfile(
var introduction: List<QuestionAndAnswer> = arrayListOf(),

var imageUrl: String? = null,

var blurredImageUrl: String? = null,
) : BaseEntity() {

fun hasCompleteIntroduction(): Boolean {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,5 @@ interface UserAlimyRepository: JpaRepository<UserAlimy, Long> {

fun findAllByUserId(userId: Long): List<UserAlimy>

fun findAllByUserIds(userIds: Set<Long>): List<UserAlimy>
fun findAllByUserIdIn(userIds: Set<Long>): List<UserAlimy>
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,12 @@ package com.nexters.bottles.app.user.repository

import com.nexters.bottles.app.user.domain.UserProfile
import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.data.jpa.repository.Query

interface UserProfileRepository : JpaRepository<UserProfile, Long> {

fun findByUserId(userId: Long): UserProfile?

@Query("SELECT up FROM UserProfile up JOIN FETCH up.user")
fun findAllWithUser(): List<UserProfile>
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ class UserAlimyService(

@Transactional(readOnly = true)
fun findAllowedUserAlimyByUserIdsAndAlimyType(userIds: Set<Long>, alimyType: AlimyType): List<UserAlimy> {
return userAlimyRepository.findAllByUserIds(userIds)
return userAlimyRepository.findAllByUserIdIn(userIds)
.filter { it.alimyType == alimyType }
.filter { it.enabled }
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,10 @@ class UserProfileService(
}

@Transactional
fun uploadImageUrl(user: User, imageUrl: String) {
fun uploadImageUrl(user: User, imageUrl: String, blurredImageUrl: String) {
profileRepository.findByUserId(user.id)?.let {
it.imageUrl = imageUrl
it.blurredImageUrl = blurredImageUrl
} ?: throw IllegalArgumentException("고객센터에 문의해주세요")
}

Expand All @@ -77,4 +78,16 @@ class UserProfileService(
profileRepository.deleteById(profile.id)
}
}

@Transactional(readOnly = true)
fun findAllWithImage(): List<UserProfile> {
return profileRepository.findAllWithUser().filter { it.imageUrl != null }
}

@Transactional
fun addBlurImageUrl(id: Long, blurredImageUrl: String) {
profileRepository.findByIdOrNull(id)?.let {
it.blurredImageUrl = blurredImageUrl
}
}
}

0 comments on commit 2cfa9d0

Please sign in to comment.