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
98 changes: 72 additions & 26 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ on:

permissions:
contents: read
id-token: write

concurrency:
group: issueissyu-prod-deploy
cancel-in-progress: false

jobs:
build:
Expand All @@ -18,10 +23,16 @@ jobs:
github.event.pull_request.base.ref == 'main')
|| github.event_name == 'workflow_dispatch'

env:
AWS_DEFAULT_REGION: ap-northeast-2
HEALTH_URL: ${{ vars.PROD_HEALTH_URL }}

steps:
# ์ฝ”๋“œ ์ฒดํฌ์•„์›ƒ
- name: Checkout
- name: Checkout main
uses: actions/checkout@v4
with:
ref: main

# JDK 21 ์„ค์น˜
- name: Set up JDK 21
Expand Down Expand Up @@ -80,25 +91,38 @@ jobs:
unzip -l deploy/deploy.zip | head -n 50
unzip -l deploy/deploy.zip | egrep -i "pom.xml|Buildfile|src/|repository/" && exit 1 || echo "OK"

# OIDC ๊ธฐ๋ฐ˜ AWS Role Assume
- name: Configure AWS credentials
id: aws-creds
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ vars.AWS_ROLE_ARN }}
aws-region: ap-northeast-2
output-credentials: true

# ํ˜„์žฌ ๋ฐฐํฌ ๋ฒ„์ „ ์ €์žฅ (๋กค๋ฐฑ์šฉ)
- name: Save current version
run: |
CURRENT=$(aws elasticbeanstalk describe-environments \
--environment-names issueissyu-backend-prod-env \
--query 'Environments[0].VersionLabel' \
--output text)

echo "CURRENT_VERSION=$CURRENT"

if [ "$CURRENT" = "None" ] || [ "$CURRENT" = "null" ]; then
CURRENT=""
fi

echo "PREVIOUS_VERSION=$CURRENT" >> $GITHUB_ENV
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
AWS_DEFAULT_REGION: ap-northeast-2

- name: Deploy to Elastic Beanstalk
id: deploy
uses: einaregilsson/beanstalk-deploy@v22
with:
aws_access_key: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws_secret_key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws_access_key: ${{ steps.aws-creds.outputs.aws-access-key-id }}
aws_secret_key: ${{ steps.aws-creds.outputs.aws-secret-access-key }}
aws_session_token: ${{ steps.aws-creds.outputs.aws-session-token }}
application_name: 'issueissyu-backend-prod'
environment_name: 'issueissyu-backend-prod-env'
region: ap-northeast-2
Expand All @@ -108,53 +132,75 @@ jobs:
wait_for_deployment: true
wait_for_environment_recovery: 180

# EB ์ƒํƒœ์™€ ๋ฌด๊ด€ํ•˜๊ฒŒ /health ์‘๋‹ต์œผ๋กœ๋งŒ ๋ฐฐํฌ ์„ฑ๊ณต ํŒ๋‹จ
# EB ์ƒํƒœ์™€ ๋ฌด๊ด€ํ•˜๊ฒŒ ์šด์˜ HTTPS URL์˜ /health ์‘๋‹ต์œผ๋กœ ๋ฐฐํฌ ์„ฑ๊ณต ํŒ๋‹จ
# ์ตœ๋Œ€ 3ํšŒ ์žฌ์‹œ๋„, ๊ฐ ์‹œ๋„๋งˆ๋‹ค 30ํšŒ health check
- name: Smoke test with retry
id: smoke_test
run: |
MAX_RETRY=3
CNAME=$(aws elasticbeanstalk describe-environments \
--environment-names issueissyu-backend-prod-env \
--query 'Environments[0].CNAME' \
--output text)
HEALTH_URL="http://${CNAME}/health"

if [ -z "$HEALTH_URL" ]; then
echo "PROD_HEALTH_URL GitHub Actions Variable์ด ์„ค์ •๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค."
exit 1
fi

echo "HEALTH_URL=$HEALTH_URL"

for attempt in $(seq 1 $MAX_RETRY); do
echo "=== ์Šค๋ชจํฌ ํ…Œ์ŠคํŠธ ์‹œ๋„ $attempt / $MAX_RETRY ==="

for i in {1..30}; do
code="$(curl -sS -o /dev/null -w '%{http_code}' $HEALTH_URL || true)"
code="$(curl -sS --connect-timeout 5 --max-time 10 -o /dev/null -w '%{http_code}' "$HEALTH_URL" || true)"
echo "health check $i: $code"

if [ "$code" = "200" ]; then
echo "health OK (์‹œ๋„ $attempt)"
exit 0
fi

sleep 10
done

echo "์‹œ๋„ $attempt ์‹คํŒจ"
if [ $attempt -lt $MAX_RETRY ]; then

if [ "$attempt" -lt "$MAX_RETRY" ]; then
echo "30์ดˆ ํ›„ ์žฌ์‹œ๋„..."
sleep 30
fi
done

echo "์ตœ๋Œ€ ์žฌ์‹œ๋„ ํšŸ์ˆ˜($MAX_RETRY) ์ดˆ๊ณผ - ๋ฐฐํฌ ์‹คํŒจ"
exit 1
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
AWS_DEFAULT_REGION: ap-northeast-2

# ์Šค๋ชจํฌ ํ…Œ์ŠคํŠธ ์ตœ์ข… ์‹คํŒจ ์‹œ ์ด์ „ ๋ฒ„์ „์œผ๋กœ ๋กค๋ฐฑ
# ๋กค๋ฐฑ ๋ช…๋ น ํ›„ EB ํ™˜๊ฒฝ ์—…๋ฐ์ดํŠธ ์™„๋ฃŒ๊นŒ์ง€ ๋Œ€๊ธฐํ•˜๊ณ , ์šด์˜ HTTPS URL๋กœ ๋‹ค์‹œ ๊ฒ€์ฆ
- name: Rollback on failure
if: failure() && env.PREVIOUS_VERSION != '' && (steps.deploy.outcome == 'failure' || steps.smoke_test.outcome == 'failure')
if: failure() && env.PREVIOUS_VERSION != '' && env.PREVIOUS_VERSION != 'None' && (steps.deploy.outcome == 'failure' || steps.smoke_test.outcome == 'failure')
run: |
echo "๋ฐฐํฌ ์‹คํŒจ - ${{ env.PREVIOUS_VERSION }} ์œผ๋กœ ๋กค๋ฐฑ"
echo "๋ฐฐํฌ ์‹คํŒจ - $PREVIOUS_VERSION ์œผ๋กœ ๋กค๋ฐฑ"

aws elasticbeanstalk update-environment \
--environment-name issueissyu-backend-prod-env \
--version-label ${{ env.PREVIOUS_VERSION }}
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
AWS_DEFAULT_REGION: ap-northeast-2
--version-label "$PREVIOUS_VERSION"

echo "๋กค๋ฐฑ ๋ฐฐํฌ ์™„๋ฃŒ ๋Œ€๊ธฐ"
aws elasticbeanstalk wait environment-updated \
--environment-names issueissyu-backend-prod-env

echo "๋กค๋ฐฑ ํ›„ smoke test ์‹œ์ž‘"
echo "HEALTH_URL=$HEALTH_URL"

for i in {1..30}; do
code="$(curl -sS --connect-timeout 5 --max-time 10 -o /dev/null -w '%{http_code}' "$HEALTH_URL" || true)"
echo "rollback health check $i: $code"

if [ "$code" = "200" ]; then
echo "rollback health OK"
exit 0
fi

sleep 10
done

echo "๋กค๋ฐฑ ํ›„์—๋„ health check ์‹คํŒจ"
exit 1
Original file line number Diff line number Diff line change
@@ -1,26 +1,52 @@
package issueissyu.backend.domain.community.service.command;

import issueissyu.backend.utils.S3.S3Utils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationManager;

import java.util.List;

@Slf4j
@Component
@RequiredArgsConstructor
public class CommunicationPinCleaner {

private final JdbcTemplate jdbcTemplate;
private final S3Utils s3Utils;

@Transactional
public void deleteByPinId(Long pinId) {
jdbcTemplate.update("DELETE FROM pin_emoji WHERE pin_id = ?", pinId);
jdbcTemplate.update("DELETE FROM declaration WHERE pin_id = ?", pinId);
jdbcTemplate.update("DELETE FROM \"comment\" WHERE pin_id = ?", pinId);
jdbcTemplate.update("DELETE FROM pin_like WHERE pin_id = ?", pinId);
jdbcTemplate.update("DELETE FROM community WHERE pin_id = ?", pinId);
jdbcTemplate.update("DELETE FROM pin_location WHERE pin_id = ?", pinId);
// DB ์‚ญ์ œ ์ „์— pin_image S3 key ์ˆ˜์ง‘
List<String> pinImageKeys = jdbcTemplate.queryForList(
"SELECT pin_s3_key FROM pin_image WHERE pin_id = ?",
String.class, pinId);

jdbcTemplate.update("DELETE FROM pin_emoji WHERE pin_id = ?", pinId);
jdbcTemplate.update("DELETE FROM declaration WHERE pin_id = ?", pinId);
jdbcTemplate.update("DELETE FROM \"comment\" WHERE pin_id = ?", pinId);
jdbcTemplate.update("DELETE FROM pin_like WHERE pin_id = ?", pinId);
jdbcTemplate.update("DELETE FROM community WHERE pin_id = ?", pinId);
jdbcTemplate.update("DELETE FROM pin_location WHERE pin_id = ?", pinId);
jdbcTemplate.update("DELETE FROM communication_pin WHERE pin_id = ?", pinId);
jdbcTemplate.update("DELETE FROM pin_image WHERE pin_id = ?", pinId);
jdbcTemplate.update("DELETE FROM pin WHERE pin_id = ?", pinId);
jdbcTemplate.update("DELETE FROM pin_image WHERE pin_id = ?", pinId);
jdbcTemplate.update("DELETE FROM pin WHERE pin_id = ?", pinId);

// DB ์‚ญ์ œ ์™„๋ฃŒ ํ›„ ํŠธ๋žœ์žญ์…˜ ์ปค๋ฐ‹์ด ์„ฑ๊ณตํ•˜๋ฉด S3 ๊ฐ์ฒด ์‚ญ์ œ
if (TransactionSynchronizationManager.isSynchronizationActive()) {
TransactionSynchronizationManager.registerSynchronization(
new TransactionSynchronization() {
@Override
public void afterCommit() {
pinImageKeys.forEach(s3Utils::deleteIfNotReserved);
}
});
} else {
pinImageKeys.forEach(s3Utils::deleteIfNotReserved);
}
Comment on lines +24 to +50

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์—์„œ ์กฐํšŒํ•œ pinImageKeys ๋ชฉ๋ก์— null์ด๋‚˜ ๋นˆ ๋ฌธ์ž์—ด์ด ํฌํ•จ๋˜์–ด ์žˆ์„ ๊ฐ€๋Šฅ์„ฑ์„ ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•ด ํ•„ํ„ฐ๋ง์„ ์ถ”๊ฐ€ํ•˜๊ณ , ์‚ญ์ œํ•  ํ‚ค๊ฐ€ ์—†์„ ๋•Œ๋Š” ๋ถˆํ•„์š”ํ•œ ํŠธ๋žœ์žญ์…˜ ๋™๊ธฐํ™” ๋“ฑ๋ก์„ ๊ฑด๋„ˆ๋›ฐ๋„๋ก ๊ฐœ์„ ํ•˜๋Š” ๊ฒƒ์ด ์•ˆ์ „ํ•ฉ๋‹ˆ๋‹ค.

        // DB ์‚ญ์ œ ์ „์— pin_image S3 key ์ˆ˜์ง‘
        List<String> pinImageKeys = jdbcTemplate.queryForList(
                "SELECT pin_s3_key FROM pin_image WHERE pin_id = ?",
                String.class, pinId).stream()
                .filter(key -> key != null && !key.isBlank())
                .toList();

        jdbcTemplate.update("DELETE FROM pin_emoji         WHERE pin_id = ?", pinId);
        jdbcTemplate.update("DELETE FROM declaration       WHERE pin_id = ?", pinId);
        jdbcTemplate.update("DELETE FROM \"comment\"       WHERE pin_id = ?", pinId);
        jdbcTemplate.update("DELETE FROM pin_like          WHERE pin_id = ?", pinId);
        jdbcTemplate.update("DELETE FROM community         WHERE pin_id = ?", pinId);
        jdbcTemplate.update("DELETE FROM pin_location      WHERE pin_id = ?", pinId);
        jdbcTemplate.update("DELETE FROM communication_pin WHERE pin_id = ?", pinId);
        jdbcTemplate.update("DELETE FROM pin_image         WHERE pin_id = ?", pinId);
        jdbcTemplate.update("DELETE FROM pin               WHERE pin_id = ?", pinId);

        // DB ์‚ญ์ œ ์™„๋ฃŒ ํ›„ ํŠธ๋žœ์žญ์…˜ ์ปค๋ฐ‹์ด ์„ฑ๊ณตํ•˜๋ฉด S3 ๊ฐ์ฒด ์‚ญ์ œ
        if (!pinImageKeys.isEmpty()) {
            if (TransactionSynchronizationManager.isSynchronizationActive()) {
                TransactionSynchronizationManager.registerSynchronization(
                        new TransactionSynchronization() {
                            @Override
                            public void afterCommit() {
                                pinImageKeys.forEach(s3Utils::deleteIfNotReserved);
                            }
                        });
            } else {
                pinImageKeys.forEach(s3Utils::deleteIfNotReserved);
            }
        }

}
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
package issueissyu.backend.domain.community.service.command;

import issueissyu.backend.domain.community.entity.CardnewsImageS3;
import issueissyu.backend.domain.community.entity.Community;
import issueissyu.backend.domain.community.enums.CommunityType;
import issueissyu.backend.domain.community.exception.CommunityException;
import issueissyu.backend.domain.community.exception.code.CommunityErrorCode;
import issueissyu.backend.domain.community.repository.CardnewsImageS3Repository;
import issueissyu.backend.domain.community.repository.CommunityRepository;
import issueissyu.backend.utils.S3.S3Utils;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationManager;

import java.util.List;

@Service
@RequiredArgsConstructor
Expand All @@ -16,6 +23,8 @@ public class CommunityCommandServiceImpl implements CommunityCommandService {

private final CommunityRepository communityRepository;
private final CommunicationPinCleaner communicationPinCleaner;
private final CardnewsImageS3Repository cardnewsImageS3Repository;
private final S3Utils s3Utils;

@Override
public void deleteCommunity(Long communityId, String uid) {
Expand Down Expand Up @@ -45,6 +54,29 @@ public void takedownCommunity(Long communityId, String uid) {
if (!community.getPin().getUser().getUid().equals(uid)) {
throw CommunityException.of(CommunityErrorCode.COMMUNITY_403_3);
}

// cardnews_image_s3๋Š” Community์— cascade๊ฐ€ ์—†์œผ๋ฏ€๋กœ ๋ช…์‹œ์ ์œผ๋กœ ๋จผ์ € ์‚ญ์ œ
// findDetailById์—์„œ left join fetch๋กœ ๋กœ๋“œ๋œ ๋ฐ์ดํ„ฐ๋ฅผ ํ™œ์šฉ
List<String> cardnewsKeys = community.getCardnewsImages().stream()
.map(CardnewsImageS3::getCardnewsImageS3Key)
.toList();
if (!cardnewsKeys.isEmpty()) {
cardnewsImageS3Repository.deleteByCommunity_CommunityId(communityId);
}

communityRepository.delete(community);

// DB ์‚ญ์ œ ์™„๋ฃŒ ํ›„ ํŠธ๋žœ์žญ์…˜ ์ปค๋ฐ‹์ด ์„ฑ๊ณตํ•˜๋ฉด S3 ๊ฐ์ฒด ์‚ญ์ œ
if (TransactionSynchronizationManager.isSynchronizationActive()) {
TransactionSynchronizationManager.registerSynchronization(
new TransactionSynchronization() {
@Override
public void afterCommit() {
cardnewsKeys.forEach(s3Utils::deleteIfNotReserved);
}
});
} else {
cardnewsKeys.forEach(s3Utils::deleteIfNotReserved);
}
Comment on lines +60 to +80

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

community.getCardnewsImages()๊ฐ€ null์„ ๋ฐ˜ํ™˜ํ•  ๊ฐ€๋Šฅ์„ฑ์— ๋Œ€๋น„ํ•˜์—ฌ ์•ˆ์ „ํ•˜๊ฒŒ ๋ฐฉ์–ด ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜๊ณ , S3 ํ‚ค๊ฐ€ ๋น„์–ด์žˆ๊ฑฐ๋‚˜ null์ธ ๊ฒฝ์šฐ๋ฅผ ํ•„ํ„ฐ๋งํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค. ๋˜ํ•œ, ์‚ญ์ œํ•  S3 ํ‚ค ๋ชฉ๋ก(cardnewsKeys)์ด ๋น„์–ด์žˆ๋‹ค๋ฉด ๋ถˆํ•„์š”ํ•˜๊ฒŒ ํŠธ๋žœ์žญ์…˜ ๋™๊ธฐํ™”(TransactionSynchronization)๋ฅผ ๋“ฑ๋กํ•˜์ง€ ์•Š๋„๋ก ๊ฐœ์„ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ถ”๊ฐ€๋กœ, ํŠธ๋žœ์žญ์…˜ ์ปค๋ฐ‹ ํ›„(afterCommit) ๋™๊ธฐ์ ์œผ๋กœ S3 ์‚ญ์ œ API๋ฅผ ํ˜ธ์ถœํ•˜๋ฉด ์™ธ๋ถ€ ๋„คํŠธ์›Œํฌ ์ง€์—ฐ์œผ๋กœ ์ธํ•ด ์š”์ฒญ ์Šค๋ ˆ๋“œ๊ฐ€ ์˜ค๋žซ๋™์•ˆ ์ฐจ๋‹จ(blocking)๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์šด์˜ ํ™˜๊ฒฝ์˜ ๋ถ€ํ•˜๊ฐ€ ๋†’์„ ๋•Œ๋Š” ์ปค๋„ฅ์…˜ ํ’€ ๊ณ ๊ฐˆ๋กœ ์ด์–ด์งˆ ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ, ์žฅ๊ธฐ์ ์œผ๋กœ๋Š” S3 ์‚ญ์ œ ์ž‘์—…์„ ๋น„๋™๊ธฐ ์Šค๋ ˆ๋“œ ํ’€(TaskExecutor)์ด๋‚˜ ๋น„๋™๊ธฐ ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ(@Async)๋ฅผ ํ†ตํ•ด ์ฒ˜๋ฆฌํ•˜๋Š” ๊ตฌ์กฐ๋กœ ๊ฐœ์„ ํ•˜๋Š” ๊ฒƒ์„ ๊ถŒ์žฅํ•ฉ๋‹ˆ๋‹ค.

        List<String> cardnewsKeys = community.getCardnewsImages() == null ? List.of() : community.getCardnewsImages().stream()
                .map(CardnewsImageS3::getCardnewsImageS3Key)
                .filter(key -> key != null && !key.isBlank())
                .toList();
        if (!cardnewsKeys.isEmpty()) {
            cardnewsImageS3Repository.deleteByCommunity_CommunityId(communityId);
        }

        communityRepository.delete(community);

        if (!cardnewsKeys.isEmpty()) {
            if (TransactionSynchronizationManager.isSynchronizationActive()) {
                TransactionSynchronizationManager.registerSynchronization(
                        new TransactionSynchronization() {
                            @Override
                            public void afterCommit() {
                                cardnewsKeys.forEach(s3Utils::deleteIfNotReserved);
                            }
                        });
            } else {
                cardnewsKeys.forEach(s3Utils::deleteIfNotReserved);
            }
        }

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import issueissyu.backend.domain.issue.entity.ProblemSolverImage;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
Expand All @@ -12,6 +13,9 @@ public interface ProblemSolverImageRepository extends JpaRepository<ProblemSolve

Optional<ProblemSolverImage> findByProblemSolver_ProblemSolverId(Long problemSolverId);

@Query("SELECT psi FROM ProblemSolverImage psi WHERE psi.problemSolver.problemSolverId IN :ids")
List<ProblemSolverImage> findAllByProblemSolver_ProblemSolverIdIn(@Param("ids") Collection<Long> ids);

@Modifying(flushAutomatically = true, clearAutomatically = true)
@Query("DELETE FROM ProblemSolverImage psi WHERE psi.problemSolver.problemSolverId IN :ids")
void deleteAllByProblemSolver_ProblemSolverIdIn(@Param("ids") Collection<Long> ids);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@
import org.postgresql.geometric.PGpoint;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationManager;

import java.util.ArrayList;
import java.time.LocalDateTime;
Expand Down Expand Up @@ -293,9 +295,27 @@ private boolean syncPinImages(Pin pin, List<PinImageItemReqDTO> items) {
boolean changed = false;
Set<String> keepUrls = items.stream().map(PinImageItemReqDTO::pinImageUrl).collect(Collectors.toSet());

// ์ œ๊ฑฐ ๋Œ€์ƒ key๋ฅผ removeIf ์ „์— ๋จผ์ € ์ˆ˜์ง‘ (orphanRemoval๋กœ DB ์‚ญ์ œ๋˜๊ธฐ ์ „)
List<String> removedKeys = pin.getPinImages().stream()
.filter(pi -> !keepUrls.contains(pi.getPinS3Url()))
.map(PinImage::getPinS3Key)
.filter(key -> key != null && !key.isBlank())
.toList();

boolean removedAny = pin.getPinImages().removeIf(pi -> !keepUrls.contains(pi.getPinS3Url()));
if (removedAny) {
changed = true;
if (TransactionSynchronizationManager.isSynchronizationActive()) {
TransactionSynchronizationManager.registerSynchronization(
new TransactionSynchronization() {
@Override
public void afterCommit() {
removedKeys.forEach(s3Utils::deleteIfNotReserved);
}
});
} else {
removedKeys.forEach(s3Utils::deleteIfNotReserved);
}
}
Comment on lines +298 to 319

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

removedAny๊ฐ€ true์ด๋”๋ผ๋„ ์‹ค์ œ ์ˆ˜์ง‘๋œ removedKeys๊ฐ€ ๋น„์–ด์žˆ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค (์˜ˆ: ์‚ญ์ œ ๋Œ€์ƒ ์ด๋ฏธ์ง€๋“ค์˜ S3 key๊ฐ€ ๋ชจ๋‘ ๋น„์–ด์žˆ๋Š” ๊ฒฝ์šฐ). ๋”ฐ๋ผ์„œ S3 ์‚ญ์ œ ๋™๊ธฐํ™” ๋“ฑ๋ก์€ removedAny ๋Œ€์‹  !removedKeys.isEmpty() ์กฐ๊ฑด์„ ๊ธฐ์ค€์œผ๋กœ ์ˆ˜ํ–‰ํ•˜๋Š” ๊ฒƒ์ด ๋” ํšจ์œจ์ ์ž…๋‹ˆ๋‹ค.

        // ์ œ๊ฑฐ ๋Œ€์ƒ key๋ฅผ removeIf ์ „์— ๋จผ์ € ์ˆ˜์ง‘ (orphanRemoval๋กœ DB ์‚ญ์ œ๋˜๊ธฐ ์ „)
        List<String> removedKeys = pin.getPinImages().stream()
                .filter(pi -> !keepUrls.contains(pi.getPinS3Url()))
                .map(PinImage::getPinS3Key)
                .filter(key -> key != null && !key.isBlank())
                .toList();

        boolean removedAny = pin.getPinImages().removeIf(pi -> !keepUrls.contains(pi.getPinS3Url()));
        if (removedAny) {
            changed = true;
        }

        if (!removedKeys.isEmpty()) {
            if (TransactionSynchronizationManager.isSynchronizationActive()) {
                TransactionSynchronizationManager.registerSynchronization(
                        new TransactionSynchronization() {
                            @Override
                            public void afterCommit() {
                                removedKeys.forEach(s3Utils::deleteIfNotReserved);
                            }
                        });
            } else {
                removedKeys.forEach(s3Utils::deleteIfNotReserved);
            }
        }


for (PinImageItemReqDTO item : items) {
Expand Down
Loading
Loading