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
10 changes: 10 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,18 @@ dependencies {

//validation
implementation 'org.springframework.boot:spring-boot-starter-validation'

//smtp
implementation 'org.springframework.boot:spring-boot-starter-mail'

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

tasks.named('test') {
useJUnitPlatform()
}

tasks.withType(JavaCompile).configureEach {
options.compilerArgs << "-parameters"
}
Original file line number Diff line number Diff line change
@@ -1,33 +1,20 @@
package com.ureca.uplait.domain.admin.service;

import static com.ureca.uplait.domain.plan.util.DescriptionUtil.createDescription;

import com.ureca.uplait.domain.admin.api.FastAPIClient;
import com.ureca.uplait.domain.admin.dto.request.AdminIPTVPlanCreateRequest;
import com.ureca.uplait.domain.admin.dto.request.AdminIPTVPlanUpdateRequest;
import com.ureca.uplait.domain.admin.dto.request.AdminInternetPlanCreateRequest;
import com.ureca.uplait.domain.admin.dto.request.AdminInternetPlanUpdateRequest;
import com.ureca.uplait.domain.admin.dto.request.AdminMobileCreateRequest;
import com.ureca.uplait.domain.admin.dto.request.AdminMobilePlanUpdateRequest;
import com.ureca.uplait.domain.admin.dto.request.*;
import com.ureca.uplait.domain.admin.dto.response.AdminPlanCreateResponse;
import com.ureca.uplait.domain.admin.dto.response.AdminPlanDeleteResponse;
import com.ureca.uplait.domain.admin.dto.response.AdminPlanDetailResponse;
import com.ureca.uplait.domain.admin.dto.response.AdminUpdateAllVectorResponse;
import com.ureca.uplait.domain.admin.repository.PlanVectorJdbcRepository;
import com.ureca.uplait.domain.email.batch.EmailBatchRunner;
import com.ureca.uplait.domain.community.entity.CommunityBenefit;
import com.ureca.uplait.domain.community.entity.CommunityBenefitPrice;
import com.ureca.uplait.domain.community.entity.PlanCommunity;
import com.ureca.uplait.domain.community.repository.CommunityBenefitPriceRepository;
import com.ureca.uplait.domain.community.repository.CommunityBenefitRepository;
import com.ureca.uplait.domain.community.repository.PlanCommunityRepository;
import com.ureca.uplait.domain.plan.dto.response.CommunityBenefitResponse;
import com.ureca.uplait.domain.plan.dto.response.IPTVPlanDetailResponse;
import com.ureca.uplait.domain.plan.dto.response.InternetPlanDetailResponse;
import com.ureca.uplait.domain.plan.dto.response.MobilePlanDetailResponse;
import com.ureca.uplait.domain.plan.dto.response.PlanCreationInfoResponse;
import com.ureca.uplait.domain.plan.dto.response.PlanDetailResponse;
import com.ureca.uplait.domain.plan.dto.response.PlanResponseFactory;
import com.ureca.uplait.domain.plan.dto.response.TagResponse;
import com.ureca.uplait.domain.plan.dto.response.*;
import com.ureca.uplait.domain.plan.entity.IPTVPlan;
import com.ureca.uplait.domain.plan.entity.InternetPlan;
import com.ureca.uplait.domain.plan.entity.MobilePlan;
Expand All @@ -40,16 +27,17 @@
import com.ureca.uplait.domain.user.repository.TagRepository;
import com.ureca.uplait.global.exception.GlobalException;
import com.ureca.uplait.global.response.ResultCode;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;
import java.util.stream.Collectors;

import static com.ureca.uplait.domain.plan.util.DescriptionUtil.createDescription;

@Service
@RequiredArgsConstructor
@Transactional
Expand All @@ -63,6 +51,7 @@ public class AdminPlanService {
private final CommunityBenefitPriceRepository communityBenefitPriceRepository;
private final PlanVectorJdbcRepository planVectorJdbcRepository;
private final FastAPIClient fastAPIClient;
private final EmailBatchRunner emailBatchRunner;

@Transactional
public AdminPlanCreateResponse createMobilePlan(AdminMobileCreateRequest request) {
Expand All @@ -82,6 +71,12 @@ public AdminPlanCreateResponse createMobilePlan(AdminMobileCreateRequest request
getPricesGroupedByBenefit(communityBenefitList));
fastAPIClient.saveVector(savedPlan, description);

// Batch 실행
String tagIdStr = tagList.stream()
.map(t -> String.valueOf(t.getId()))
.collect(Collectors.joining(","));
emailBatchRunner.runEmailBatchAsync(plan.getId(), tagIdStr);

return new AdminPlanCreateResponse(savedPlan.getId());
}

Expand All @@ -103,6 +98,12 @@ public AdminPlanCreateResponse createInternetPlan(AdminInternetPlanCreateRequest
getPricesGroupedByBenefit(communityBenefitList));
fastAPIClient.saveVector(savedPlan, description);

// Batch 실행
String tagIdStr = tagList.stream()
.map(String::valueOf)
.collect(Collectors.joining(","));
emailBatchRunner.runEmailBatchAsync(plan.getId(), tagIdStr);

return new AdminPlanCreateResponse(savedPlan.getId());
}

Expand All @@ -124,6 +125,12 @@ public AdminPlanCreateResponse createIptvPlan(AdminIPTVPlanCreateRequest request
getPricesGroupedByBenefit(communityBenefitList));
fastAPIClient.saveVector(savedPlan, description);

// Batch 실행
String tagIdStr = tagList.stream()
.map(String::valueOf)
.collect(Collectors.joining(","));
emailBatchRunner.runEmailBatchAsync(plan.getId(), tagIdStr);

return new AdminPlanCreateResponse(savedPlan.getId());
}

Expand Down Expand Up @@ -231,7 +238,7 @@ public AdminPlanDetailResponse getPlanDetail(Long planId) {

public PlanDetailResponse getTypedPlanDetail(String type, Long planId) {
Plan plan = getPlan(planId);
return PlanResponseFactory.from(plan, false);
return PlanResponseFactory.from(plan, null,false);
}

@Transactional
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.ureca.uplait.domain.email.batch;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobParametersBuilder;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;

@Slf4j
@Component
@RequiredArgsConstructor
public class EmailBatchRunner {

private final JobLauncher jobLauncher;
private final Job emailSendJob;

@Async
public void runEmailBatchAsync(Long planId, String tagIdsStr) {
try {
log.info("runEmailBatchRunner 실행");
jobLauncher.run(
emailSendJob,
new JobParametersBuilder()
.addString("timestamp", String.valueOf(System.currentTimeMillis()))
.addString("planId", planId.toString())
.addString("tagIds", tagIdsStr)
.toJobParameters()
);
} catch (Exception e) {
log.error("이메일 배치 실행 실패: {}", e.getMessage(), e);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package com.ureca.uplait.domain.email.batch;

import com.ureca.uplait.domain.user.entity.User;
import com.ureca.uplait.domain.user.repository.UserJdbcRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.batch.item.ItemReader;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;

import java.util.Iterator;
import java.util.List;

@RequiredArgsConstructor
public class JdbcPagingUserReader implements ItemReader<User> {

private final UserJdbcRepository userJdbcRepository;
private final Long planId;
private final int pageSize;

private int currentPage = 0;
private Iterator<User> currentIterator;

@Override
public User read() {
if (currentIterator == null || !currentIterator.hasNext()) {
Pageable pageable = PageRequest.of(currentPage++, pageSize);
List<User> users = userJdbcRepository.findUsersWithMatchingTopTagsByPlanId(planId, pageable);
if (users.isEmpty()) return null; // 배치 종료 조건
currentIterator = users.iterator();
}

return currentIterator.next();
}
}
11 changes: 11 additions & 0 deletions src/main/java/com/ureca/uplait/domain/email/entity/Email.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.ureca.uplait.domain.email.entity;

import lombok.AllArgsConstructor;
import lombok.Getter;

@Getter
@AllArgsConstructor
public class Email {
private String title;
private String content;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package com.ureca.uplait.domain.email.util;

import com.ureca.uplait.domain.email.entity.Email;
import com.ureca.uplait.domain.plan.entity.IPTVPlan;
import com.ureca.uplait.domain.plan.entity.InternetPlan;
import com.ureca.uplait.domain.plan.entity.MobilePlan;
import com.ureca.uplait.domain.plan.entity.Plan;
import com.ureca.uplait.domain.user.entity.User;
import com.ureca.uplait.global.exception.GlobalException;

import static com.ureca.uplait.global.response.ResultCode.INVALID_PLAN;

public class EmailTemplateUtil {

public static Email buildEmail(User user, Plan plan) {
String title = String.format("[Uplait] 신규 요금제 '%s' 출시! 특별 혜택 확인하세요!", plan.getPlanName());
String content = String.format(
"안녕하세요 %s님.\n\n" +
"Uplait에서 %s님이 관심있어하시는 주제와 관련된 새로운 요금제 '%s'가 출시되었습니다!\n" +
"요금: %,d원\n" +
buildDetail(plan) +
"아래 링크에서 자세한 혜택을 확인해보세요.\n\n" +
"자세히 보기: https://uplait.site/%s/plan/%s" +
"\n감사합니다,\nUplait 드림",
user.getName() != null ? user.getName() : "고객",
user.getName() != null ? user.getName() : "고객",
plan.getPlanName(),
plan.getPlanPrice(),
getType(plan),
plan.getId()
);
return new Email(title, content);
}

private static String buildDetail(Plan plan) {
if(plan instanceof MobilePlan mp) {
return buildMobile(mp);
} else if(plan instanceof InternetPlan ip) {
return buildInternet(ip);
} else if(plan instanceof IPTVPlan ip) {
return buildIptv(ip);
} else {
throw new GlobalException(INVALID_PLAN);
}
}

private static String getType(Plan plan) {
if(plan instanceof MobilePlan) {
return "mobile";
} else if(plan instanceof InternetPlan) {
return "internet";
} else if(plan instanceof IPTVPlan) {
return "iptv";
} else {
throw new GlobalException(INVALID_PLAN);
}
}

private static String buildMobile(MobilePlan mp) {
return String.format(
"데이터: %s\n" +
"음성통화: %s\n" +
"문자: %s\n\n",
mp.getData(),
mp.getVoiceCall(),
mp.getMessage()
);
}

private static String buildInternet(InternetPlan ip) {
return String.format(
"인터넷 속도: %s\n",
ip.getVelocity()
);
}

private static String buildIptv(IPTVPlan ip) {
return String.format(
"채널 수: %s\n",
ip.getChannel()
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@

import com.ureca.uplait.domain.plan.entity.IPTVPlan;
import io.swagger.v3.oas.annotations.media.Schema;
import java.util.List;
import lombok.Getter;

import java.util.List;

@Getter
@Schema(description = "IPTV 요금제 상세")
public class IPTVPlanDetailResponse extends PlanDetailResponse {
Expand All @@ -22,8 +23,8 @@ public class IPTVPlanDetailResponse extends PlanDetailResponse {
@Schema(description = "결합 혜택", example = "가족결합")
private List<CommunityBenefitResponse> communityBenefitList;

public IPTVPlanDetailResponse(IPTVPlan plan, boolean inUse) {
super(plan, inUse);
public IPTVPlanDetailResponse(IPTVPlan plan, List<Long> communityIdList, boolean inUse) {
super(plan, communityIdList, inUse);
this.channel = plan.getChannel();
this.iptvDiscount = plan.getPlanPrice() * (100 - plan.getIptvDiscountRate()) / 100;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@

import com.ureca.uplait.domain.plan.entity.InternetPlan;
import io.swagger.v3.oas.annotations.media.Schema;
import java.util.List;
import lombok.Getter;

import java.util.List;

@Getter
@Schema(description = "인터넷 요금제 상세")
public class InternetPlanDetailResponse extends PlanDetailResponse {
Expand All @@ -21,8 +22,8 @@ public class InternetPlanDetailResponse extends PlanDetailResponse {
@Schema(description = "결합 혜택", example = "가족결합")
private List<CommunityBenefitResponse> communityBenefitList;

public InternetPlanDetailResponse(InternetPlan plan, boolean inUse) {
super(plan, inUse);
public InternetPlanDetailResponse(InternetPlan plan, List<Long> communityIdList, boolean inUse) {
super(plan, communityIdList, inUse);
this.velocity = plan.getVelocity();
this.internetDiscount = plan.getInternetDiscountRate();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@
import com.ureca.uplait.domain.plan.entity.MediaBenefit;
import com.ureca.uplait.domain.plan.entity.MobilePlan;
import io.swagger.v3.oas.annotations.media.Schema;
import java.util.List;
import lombok.Getter;

import java.util.List;

@Getter
@Schema(description = "모바일 요금제 상세")
public class MobilePlanDetailResponse extends PlanDetailResponse {
Expand Down Expand Up @@ -40,8 +41,8 @@ public class MobilePlanDetailResponse extends PlanDetailResponse {
@Schema(description = "결합 혜택", example = "가족결합")
private List<CommunityBenefitResponse> communityBenefitList;

public MobilePlanDetailResponse(MobilePlan plan, boolean inUse) {
super(plan, inUse);
public MobilePlanDetailResponse(MobilePlan plan, List<Long> communityIdList, boolean inUse) {
super(plan, communityIdList, inUse);
this.data = plan.getData();
this.sharedData = plan.getSharedData();
this.voiceCall = plan.getVoiceCall();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;

import java.util.List;

@Getter
@JsonInclude(JsonInclude.Include.NON_NULL)
@Schema(description = "요금제 상세 조회 결과")
Expand Down Expand Up @@ -37,14 +39,18 @@ public abstract class PlanDetailResponse {
@Schema(description = "플랜 설명", example = "너무 좋은 요금제")
private String description;

@Schema(description = "요금제의 결합 상품", example = "1, 2, 3")
private List<Long> communityIdList;


protected PlanDetailResponse(Plan plan, boolean inUse) {
protected PlanDetailResponse(Plan plan, List<Long> communityIdList, boolean inUse) {
this.planId = plan.getId();
this.planName = plan.getPlanName();
this.planPrice = plan.getPlanPrice();
this.planBenefit = plan.getPlanBenefit();
this.availability = plan.getAvailability();
this.description = plan.getDescription();
this.communityIdList = communityIdList;
this.inUse = inUse;
}

Expand Down
Loading
Loading