feat : thêm bao cao
This commit is contained in:
parent
abd31bf956
commit
f9626aa701
|
|
@ -4,10 +4,12 @@ import org.springframework.boot.SpringApplication;
|
|||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.autoconfigure.domain.EntityScan;
|
||||
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
|
||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||
import org.springframework.transaction.annotation.EnableTransactionManagement;
|
||||
|
||||
@SpringBootApplication(scanBasePackages = "com.vega.hrm")
|
||||
@EnableTransactionManagement
|
||||
@EnableScheduling
|
||||
public class VegaHrmAuthApplication {
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(VegaHrmAuthApplication.class, args);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,102 @@
|
|||
package com.vega.hrm.service;
|
||||
|
||||
import com.vega.hrm.core.entities.UserGoogleToken;
|
||||
import com.vega.hrm.core.helpers.LogHelper;
|
||||
import com.vega.hrm.core.repositories.UserGoogleTokenRepository;
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class TokenRefreshScheduledJob {
|
||||
|
||||
private final UserGoogleTokenRepository userGoogleTokenRepository;
|
||||
private final TokenRefreshService tokenRefreshService;
|
||||
|
||||
|
||||
@Scheduled(cron = "0 */30 * * * ?")
|
||||
public void refreshExpiredTokens() {
|
||||
LogHelper.info("Bắt đầu job refresh access token hết hạn");
|
||||
|
||||
try {
|
||||
Instant now = Instant.now();
|
||||
|
||||
// Tìm các token đã hết hạn nhưng refresh token vẫn còn hiệu lực
|
||||
List<UserGoogleToken> expiredTokens = userGoogleTokenRepository.findExpiredTokensWithValidRefreshToken(now);
|
||||
|
||||
if (expiredTokens == null || expiredTokens.isEmpty()) {
|
||||
LogHelper.info("Không có token nào cần refresh");
|
||||
return;
|
||||
}
|
||||
|
||||
LogHelper.info("Tìm thấy " + expiredTokens.size() + " token cần refresh");
|
||||
|
||||
int successCount = 0;
|
||||
int failCount = 0;
|
||||
|
||||
for (UserGoogleToken token : expiredTokens) {
|
||||
try {
|
||||
boolean success = tokenRefreshService.refreshAccessToken(token);
|
||||
if (success) {
|
||||
successCount++;
|
||||
} else {
|
||||
failCount++;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LogHelper.error("Lỗi không mong đợi khi refresh token cho email: " + token.getEmail() + " - " + e.getMessage());
|
||||
failCount++;
|
||||
}
|
||||
}
|
||||
|
||||
LogHelper.info("Hoàn thành job refresh token. Thành công: " + successCount + ", Thất bại: " + failCount);
|
||||
|
||||
} catch (Exception e) {
|
||||
LogHelper.error("Lỗi nghiêm trọng trong job refresh token: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Scheduled(cron = "0 */10 * * * ?")
|
||||
public void refreshTokensExpiringSoon() {
|
||||
LogHelper.info("Bắt đầu job refresh token sắp hết hạn");
|
||||
|
||||
try {
|
||||
Instant now = Instant.now();
|
||||
// Refresh các token sẽ hết hạn trong vòng 15 phút tới
|
||||
Instant expiryTime = now.plusSeconds(15 * 60);
|
||||
|
||||
List<UserGoogleToken> expiringSoonTokens = userGoogleTokenRepository.findTokensExpiringSoon(expiryTime, now);
|
||||
|
||||
if (expiringSoonTokens == null || expiringSoonTokens.isEmpty()) {
|
||||
LogHelper.info("Không có token nào sắp hết hạn");
|
||||
return;
|
||||
}
|
||||
|
||||
LogHelper.info("Tìm thấy " + expiringSoonTokens.size() + " token sắp hết hạn");
|
||||
|
||||
int successCount = 0;
|
||||
int failCount = 0;
|
||||
|
||||
for (UserGoogleToken token : expiringSoonTokens) {
|
||||
try {
|
||||
boolean success = tokenRefreshService.refreshAccessToken(token);
|
||||
if (success) {
|
||||
successCount++;
|
||||
} else {
|
||||
failCount++;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LogHelper.error("Lỗi không mong đợi khi refresh token sắp hết hạn cho email: " + token.getEmail() + " - " + e.getMessage());
|
||||
failCount++;
|
||||
}
|
||||
}
|
||||
|
||||
LogHelper.info("Hoàn thành job refresh token sắp hết hạn. Thành công: " + successCount + ", Thất bại: " + failCount);
|
||||
|
||||
} catch (Exception e) {
|
||||
LogHelper.error("Lỗi nghiêm trọng trong job refresh token sắp hết hạn: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,113 @@
|
|||
package com.vega.hrm.service;
|
||||
|
||||
import com.google.api.client.auth.oauth2.TokenResponse;
|
||||
import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;
|
||||
import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport;
|
||||
import com.google.api.client.http.javanet.NetHttpTransport;
|
||||
import com.google.api.client.json.JsonFactory;
|
||||
import com.google.api.client.json.jackson2.JacksonFactory;
|
||||
import com.vega.hrm.core.component.TokenStore;
|
||||
import com.vega.hrm.core.dto.GoogleOAuthConfig;
|
||||
import com.vega.hrm.core.entities.UserGoogleToken;
|
||||
import com.vega.hrm.core.helpers.LogHelper;
|
||||
import com.vega.hrm.core.repositories.UserGoogleTokenRepository;
|
||||
import java.io.IOException;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.time.Instant;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class TokenRefreshService {
|
||||
|
||||
private static final JsonFactory JSON_FACTORY = JacksonFactory.getDefaultInstance();
|
||||
private final UserGoogleTokenRepository userGoogleTokenRepository;
|
||||
private final GoogleOAuthConfig googleOAuthConfig;
|
||||
private final TokenStore tokenStore;
|
||||
|
||||
/**
|
||||
* Refresh access token bằng refresh token
|
||||
* @param userGoogleToken Token cần refresh
|
||||
* @return true nếu refresh thành công, false nếu thất bại
|
||||
*/
|
||||
@Transactional
|
||||
public boolean refreshAccessToken(UserGoogleToken userGoogleToken) {
|
||||
if (userGoogleToken == null || userGoogleToken.getRefreshToken() == null) {
|
||||
LogHelper.error("Không thể refresh token: token hoặc refresh token null");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Kiểm tra refresh token có còn hiệu lực không
|
||||
if (userGoogleToken.getRefreshTokenExpiresAt() != null
|
||||
&& userGoogleToken.getRefreshTokenExpiresAt().isBefore(Instant.now())) {
|
||||
LogHelper.error("Refresh token đã hết hạn cho email: " + userGoogleToken.getEmail());
|
||||
return false;
|
||||
}
|
||||
|
||||
NetHttpTransport httpTransport = null;
|
||||
try {
|
||||
httpTransport = GoogleNetHttpTransport.newTrustedTransport();
|
||||
} catch (GeneralSecurityException | IOException e) {
|
||||
LogHelper.error("Lỗi khi tạo HTTP transport: " + e.getMessage());
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
// Tạo GoogleCredential với refresh token để tự động refresh
|
||||
GoogleCredential credential = new GoogleCredential.Builder()
|
||||
.setTransport(httpTransport)
|
||||
.setJsonFactory(JSON_FACTORY)
|
||||
.setClientSecrets(googleOAuthConfig.clientId, googleOAuthConfig.clientSecret)
|
||||
.build()
|
||||
.setRefreshToken(userGoogleToken.getRefreshToken());
|
||||
|
||||
// Thử refresh token - GoogleCredential sẽ tự động refresh nếu cần
|
||||
if (!credential.refreshToken()) {
|
||||
LogHelper.error("Không thể refresh token cho email: " + userGoogleToken.getEmail());
|
||||
return false;
|
||||
}
|
||||
|
||||
// Lấy token response từ credential đã refresh
|
||||
String newAccessToken = credential.getAccessToken();
|
||||
Long expiresInSeconds = credential.getExpiresInSeconds();
|
||||
|
||||
// Cập nhật token mới vào database
|
||||
Instant now = Instant.now();
|
||||
userGoogleToken.setAccessToken(newAccessToken);
|
||||
userGoogleToken.setExpiresIn(expiresInSeconds);
|
||||
userGoogleToken.setUpdatedAt(now);
|
||||
|
||||
if (expiresInSeconds != null) {
|
||||
userGoogleToken.setExpiresAt(now.plusSeconds(expiresInSeconds));
|
||||
}
|
||||
|
||||
userGoogleTokenRepository.save(userGoogleToken);
|
||||
|
||||
// Tạo TokenResponse để cập nhật vào TokenStore cache
|
||||
TokenResponse tokenResponse = new TokenResponse()
|
||||
.setAccessToken(newAccessToken)
|
||||
.setRefreshToken(userGoogleToken.getRefreshToken())
|
||||
.setExpiresInSeconds(expiresInSeconds)
|
||||
.setTokenType(userGoogleToken.getTokenType())
|
||||
.setScope(userGoogleToken.getScope());
|
||||
|
||||
if (userGoogleToken.getRefreshTokenExpiresIn() != null) {
|
||||
tokenResponse.set("refresh_token_expires_in", userGoogleToken.getRefreshTokenExpiresIn());
|
||||
}
|
||||
|
||||
tokenStore.storeToken(userGoogleToken.getEmail(), tokenResponse);
|
||||
|
||||
LogHelper.info("Đã refresh thành công access token cho email: " + userGoogleToken.getEmail());
|
||||
return true;
|
||||
|
||||
} catch (IOException e) {
|
||||
LogHelper.error("Lỗi khi refresh token cho email: " + userGoogleToken.getEmail() + " - " + e.getMessage());
|
||||
return false;
|
||||
} catch (Exception e) {
|
||||
LogHelper.error("Lỗi không mong đợi khi refresh token cho email: " + userGoogleToken.getEmail() + " - " + e.getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
package com.vega.hrm.core.entities;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.Table;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.validation.constraints.Size;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDate;
|
||||
import java.util.UUID;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.hibernate.annotations.ColumnDefault;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@Entity
|
||||
@Table(name = "revenue_data")
|
||||
public class RevenueData {
|
||||
|
||||
@Id
|
||||
@ColumnDefault("gen_random_uuid()")
|
||||
@Column(name = "id", nullable = false)
|
||||
private UUID id;
|
||||
|
||||
@Size(max = 255)
|
||||
@NotNull
|
||||
@Column(name = "email", nullable = false, length = 255)
|
||||
private String email;
|
||||
|
||||
@NotNull
|
||||
@Column(name = "revenue_date", nullable = false)
|
||||
private LocalDate revenueDate;
|
||||
|
||||
@Column(name = "estimated_revenue")
|
||||
private Double estimatedRevenue;
|
||||
|
||||
@ColumnDefault("CURRENT_TIMESTAMP")
|
||||
@Column(name = "created_at", nullable = false)
|
||||
private Instant createdAt;
|
||||
|
||||
@ColumnDefault("CURRENT_TIMESTAMP")
|
||||
@Column(name = "updated_at")
|
||||
private Instant updatedAt;
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
package com.vega.hrm.core.repositories;
|
||||
|
||||
import com.vega.hrm.core.entities.RevenueData;
|
||||
import java.time.LocalDate;
|
||||
import java.util.UUID;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@Repository
|
||||
public interface RevenueDataRepository extends JpaRepository<RevenueData, UUID> {
|
||||
|
||||
@Query("SELECT r FROM RevenueData r WHERE r.email = :email " +
|
||||
"AND r.revenueDate >= :startDate AND r.revenueDate <= :endDate " +
|
||||
"ORDER BY r.revenueDate DESC")
|
||||
Page<RevenueData> findByEmailAndDateRange(
|
||||
@Param("email") String email,
|
||||
@Param("startDate") LocalDate startDate,
|
||||
@Param("endDate") LocalDate endDate,
|
||||
Pageable pageable
|
||||
);
|
||||
|
||||
RevenueData findByEmailAndRevenueDate(String email, LocalDate revenueDate);
|
||||
}
|
||||
|
|
@ -2,10 +2,29 @@ package com.vega.hrm.core.repositories;
|
|||
|
||||
import com.vega.hrm.core.entities.BoUser;
|
||||
import com.vega.hrm.core.entities.UserGoogleToken;
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
import org.springframework.stereotype.Repository;
|
||||
@Repository
|
||||
public interface UserGoogleTokenRepository extends JpaRepository<UserGoogleToken, UUID> {
|
||||
UserGoogleToken findUserGoogleTokenByEmail(String user);
|
||||
|
||||
@Query("SELECT DISTINCT u.email FROM UserGoogleToken u WHERE u.email IS NOT NULL")
|
||||
List<String> findAllDistinctEmails();
|
||||
|
||||
/**
|
||||
* Tìm các token đã hết hạn (expires_at < now) nhưng refresh token vẫn còn hiệu lực (refresh_token_expires_at > now)
|
||||
*/
|
||||
@Query("SELECT u FROM UserGoogleToken u WHERE u.expiresAt < :now AND u.refreshTokenExpiresAt > :now AND u.refreshToken IS NOT NULL")
|
||||
List<UserGoogleToken> findExpiredTokensWithValidRefreshToken(@Param("now") Instant now);
|
||||
|
||||
/**
|
||||
* Tìm các token sắp hết hạn (trong vòng X phút)
|
||||
*/
|
||||
@Query("SELECT u FROM UserGoogleToken u WHERE u.expiresAt < :expiryTime AND u.refreshTokenExpiresAt > :now AND u.refreshToken IS NOT NULL")
|
||||
List<UserGoogleToken> findTokensExpiringSoon(@Param("expiryTime") Instant expiryTime, @Param("now") Instant now);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ dependencies {
|
|||
annotationProcessor 'org.projectlombok:lombok:1.18.38'
|
||||
implementation project(":vega-hrm-core")
|
||||
implementation 'jakarta.annotation:jakarta.annotation-api:2.1.1'
|
||||
implementation(platform("org.springframework.boot:spring-boot-dependencies:3.4.0"))
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,10 +3,12 @@ package com.vega.hrm.report;
|
|||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||
import org.springframework.transaction.annotation.EnableTransactionManagement;
|
||||
|
||||
@SpringBootApplication(scanBasePackages = "com.vega.hrm")
|
||||
@EnableTransactionManagement
|
||||
@EnableScheduling
|
||||
public class VegaHrmReportApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
|
|
|
|||
|
|
@ -5,11 +5,13 @@ import com.google.api.client.json.jackson2.JacksonFactory;
|
|||
import com.vega.hrm.core.component.TokenStore;
|
||||
import com.vega.hrm.core.dto.GoogleOAuthConfig;
|
||||
import com.vega.hrm.core.models.responses.BaseResponse;
|
||||
import com.vega.hrm.core.models.responses.PagedListResponse;
|
||||
import com.vega.hrm.report.request.GetDragRevenueRequest;
|
||||
import com.vega.hrm.report.request.GetRevenueRequest;
|
||||
import com.vega.hrm.report.response.ReportTypeDto;
|
||||
import com.vega.hrm.report.response.RevenueDataDto;
|
||||
import com.vega.hrm.report.serivce.CreateReportingJobService;
|
||||
import com.vega.hrm.report.serivce.RevenueDataService;
|
||||
import com.vega.hrm.report.serivce.YouTubeReportService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
|
|
@ -19,6 +21,9 @@ import java.io.IOException;
|
|||
import java.security.GeneralSecurityException;
|
||||
import java.util.List;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
|
|
@ -36,6 +41,7 @@ public class ReportGoogleController {
|
|||
|
||||
private final CreateReportingJobService createReportingJob;
|
||||
private final YouTubeReportService youTubeReportService;
|
||||
private final RevenueDataService revenueDataService;
|
||||
private final GoogleOAuthConfig googleOAuthConfig;
|
||||
private final TokenStore tokenStore;
|
||||
|
||||
|
|
@ -100,27 +106,37 @@ public class ReportGoogleController {
|
|||
}
|
||||
|
||||
@PostMapping("/revenue")
|
||||
@Operation(summary = "Lấy dữ liệu doanh thu", description = "Lấy dữ liệu doanh thu ước tính từ YouTube Analytics API theo khoảng thời gian.")
|
||||
public ResponseEntity<BaseResponse<List<RevenueDataDto>>> getRevenueData(
|
||||
@Operation(summary = "Lấy dữ liệu doanh thu", description = "Lấy dữ liệu doanh thu ước tính từ database theo khoảng thời gian với phân trang.")
|
||||
public ResponseEntity<PagedListResponse<RevenueDataDto>> getRevenueData(
|
||||
@Valid @RequestBody GetRevenueRequest request
|
||||
) throws GeneralSecurityException, IOException {
|
||||
) {
|
||||
if (tokenStore.getTokenResponse(request.getEmail()) == null) {
|
||||
return ResponseEntity.ok(BaseResponse.invalid("Không tìm thấy token Google cho email đã cung cấp"));
|
||||
return ResponseEntity.ok(
|
||||
PagedListResponse.<RevenueDataDto>builder()
|
||||
.code("01")
|
||||
.message("Không tìm thấy token Google cho email đã cung cấp")
|
||||
.build()
|
||||
);
|
||||
}
|
||||
|
||||
List<RevenueDataDto> revenueData = youTubeReportService.getRevenue(
|
||||
// Tạo Pageable từ request
|
||||
int pageIndex = request.getPageIndex() != null ? request.getPageIndex() : 1;
|
||||
int pageSize = request.getPageSize() != null ? request.getPageSize() : 20;
|
||||
Pageable pageable = PageRequest.of(pageIndex - 1, pageSize, Sort.by(Sort.Direction.DESC, "revenueDate"));
|
||||
|
||||
// Lấy dữ liệu từ database với phân trang
|
||||
var pagedList = revenueDataService.getRevenueData(
|
||||
request.getEmail(),
|
||||
request.getStartDate(),
|
||||
request.getEndDate(),
|
||||
GoogleNetHttpTransport.newTrustedTransport(),
|
||||
JacksonFactory.getDefaultInstance()
|
||||
pageable
|
||||
);
|
||||
|
||||
return ResponseEntity.ok(
|
||||
BaseResponse.<List<RevenueDataDto>>builder()
|
||||
PagedListResponse.<RevenueDataDto>builder()
|
||||
.code("00")
|
||||
.message("Lấy dữ liệu doanh thu thành công")
|
||||
.data(revenueData)
|
||||
.data(pagedList)
|
||||
.build()
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
package com.vega.hrm.report.request;
|
||||
|
||||
import jakarta.validation.constraints.Min;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.Positive;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
|
|
@ -15,5 +17,11 @@ public class GetRevenueRequest {
|
|||
|
||||
@NotBlank(message = "Ngày kết thúc không được để trống")
|
||||
private String endDate; // format: YYYY-MM-DD
|
||||
|
||||
@Min(value = 1, message = "Số trang phải lớn hơn 0")
|
||||
private Integer pageIndex = 1;
|
||||
|
||||
@Positive(message = "Kích thước trang phải lớn hơn 0")
|
||||
private Integer pageSize = 20;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,160 @@
|
|||
package com.vega.hrm.report.serivce;
|
||||
|
||||
import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport;
|
||||
import com.google.api.client.json.jackson2.JacksonFactory;
|
||||
import com.vega.hrm.core.component.TokenStore;
|
||||
import com.vega.hrm.core.helpers.LogHelper;
|
||||
import com.vega.hrm.core.repositories.UserGoogleTokenRepository;
|
||||
import java.io.IOException;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.time.LocalDate;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.List;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class RevenueDataScheduledJob {
|
||||
|
||||
private final UserGoogleTokenRepository userGoogleTokenRepository;
|
||||
private final TokenStore tokenStore;
|
||||
private final YouTubeReportService youTubeReportService;
|
||||
private final RevenueDataService revenueDataService;
|
||||
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
|
||||
|
||||
/**
|
||||
* Job chạy hàng ngày lúc 2:00 AM để lấy và lưu dữ liệu doanh thu của ngày hôm qua
|
||||
* Cron: 0 0 2 * * ? - Chạy lúc 2:00 AM mỗi ngày
|
||||
*/
|
||||
@Scheduled(cron = "0 0 2 * * ?")
|
||||
public void syncRevenueDataDaily() {
|
||||
LogHelper.info("Bắt đầu job đồng bộ dữ liệu doanh thu hàng ngày");
|
||||
|
||||
try {
|
||||
// Lấy tất cả email có token Google
|
||||
List<String> emails = userGoogleTokenRepository.findAllDistinctEmails();
|
||||
|
||||
if (emails == null || emails.isEmpty()) {
|
||||
LogHelper.info("Không tìm thấy email nào có token Google để đồng bộ");
|
||||
return;
|
||||
}
|
||||
|
||||
// Lấy dữ liệu của ngày hôm qua
|
||||
LocalDate yesterday = LocalDate.now().minusDays(1);
|
||||
String dateStr = yesterday.format(DATE_FORMATTER);
|
||||
|
||||
LogHelper.info("Đang đồng bộ dữ liệu doanh thu cho ngày: " + dateStr);
|
||||
|
||||
int successCount = 0;
|
||||
int failCount = 0;
|
||||
|
||||
for (String email : emails) {
|
||||
try {
|
||||
if (tokenStore.getTokenResponse(email) == null) {
|
||||
LogHelper.info("Không tìm thấy token cho email: " + email + ", bỏ qua");
|
||||
failCount++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Lấy dữ liệu từ YouTube API
|
||||
List<com.vega.hrm.report.response.RevenueDataDto> revenueData = youTubeReportService.getRevenue(
|
||||
email,
|
||||
dateStr,
|
||||
dateStr,
|
||||
GoogleNetHttpTransport.newTrustedTransport(),
|
||||
JacksonFactory.getDefaultInstance()
|
||||
);
|
||||
|
||||
if (revenueData != null && !revenueData.isEmpty()) {
|
||||
// Lưu vào database
|
||||
revenueDataService.saveRevenueData(email, revenueData);
|
||||
LogHelper.info("Đã đồng bộ thành công dữ liệu doanh thu cho email: " + email);
|
||||
successCount++;
|
||||
} else {
|
||||
LogHelper.info("Không có dữ liệu doanh thu cho email: " + email + " vào ngày " + dateStr);
|
||||
successCount++; // Vẫn tính là success vì không có lỗi
|
||||
}
|
||||
|
||||
} catch (GeneralSecurityException | IOException e) {
|
||||
LogHelper.error("Lỗi khi đồng bộ dữ liệu doanh thu cho email: " + email + " - " + e.getMessage());
|
||||
failCount++;
|
||||
} catch (Exception e) {
|
||||
LogHelper.error("Lỗi không mong đợi khi đồng bộ dữ liệu doanh thu cho email: " + email + " - " + e.getMessage());
|
||||
failCount++;
|
||||
}
|
||||
}
|
||||
|
||||
LogHelper.info("Hoàn thành job đồng bộ dữ liệu doanh thu. Thành công: " + successCount + ", Thất bại: " + failCount);
|
||||
|
||||
} catch (Exception e) {
|
||||
LogHelper.error("Lỗi nghiêm trọng trong job đồng bộ dữ liệu doanh thu: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Job để đồng bộ lại dữ liệu của các ngày trước (chạy mỗi tuần một lần vào Chủ nhật lúc 3:00 AM)
|
||||
* Có thể dùng để đồng bộ lại dữ liệu của 7 ngày gần nhất nếu có thiếu sót
|
||||
*/
|
||||
@Scheduled(cron = "0 0 3 * * SUN")
|
||||
public void syncRevenueDataWeekly() {
|
||||
LogHelper.info("Bắt đầu job đồng bộ lại dữ liệu doanh thu hàng tuần");
|
||||
|
||||
try {
|
||||
List<String> emails = userGoogleTokenRepository.findAllDistinctEmails();
|
||||
|
||||
if (emails == null || emails.isEmpty()) {
|
||||
LogHelper.info("Không tìm thấy email nào có token Google để đồng bộ");
|
||||
return;
|
||||
}
|
||||
|
||||
// Đồng bộ lại dữ liệu của 7 ngày gần nhất
|
||||
LocalDate endDate = LocalDate.now().minusDays(1);
|
||||
LocalDate startDate = endDate.minusDays(6);
|
||||
String startDateStr = startDate.format(DATE_FORMATTER);
|
||||
String endDateStr = endDate.format(DATE_FORMATTER);
|
||||
|
||||
LogHelper.info("Đang đồng bộ lại dữ liệu doanh thu từ " + startDateStr + " đến " + endDateStr);
|
||||
|
||||
int successCount = 0;
|
||||
int failCount = 0;
|
||||
|
||||
for (String email : emails) {
|
||||
try {
|
||||
if (tokenStore.getTokenResponse(email) == null) {
|
||||
LogHelper.info("Không tìm thấy token cho email: " + email + ", bỏ qua");
|
||||
failCount++;
|
||||
continue;
|
||||
}
|
||||
|
||||
List<com.vega.hrm.report.response.RevenueDataDto> revenueData = youTubeReportService.getRevenue(
|
||||
email,
|
||||
startDateStr,
|
||||
endDateStr,
|
||||
GoogleNetHttpTransport.newTrustedTransport(),
|
||||
JacksonFactory.getDefaultInstance()
|
||||
);
|
||||
|
||||
if (revenueData != null && !revenueData.isEmpty()) {
|
||||
revenueDataService.saveRevenueData(email, revenueData);
|
||||
LogHelper.info("Đã đồng bộ lại thành công dữ liệu doanh thu cho email: " + email);
|
||||
successCount++;
|
||||
}
|
||||
|
||||
} catch (GeneralSecurityException | IOException e) {
|
||||
LogHelper.error("Lỗi khi đồng bộ lại dữ liệu doanh thu cho email: " + email + " - " + e.getMessage());
|
||||
failCount++;
|
||||
} catch (Exception e) {
|
||||
LogHelper.error("Lỗi không mong đợi khi đồng bộ lại dữ liệu doanh thu cho email: " + email + " - " + e.getMessage());
|
||||
failCount++;
|
||||
}
|
||||
}
|
||||
|
||||
LogHelper.info("Hoàn thành job đồng bộ lại dữ liệu doanh thu. Thành công: " + successCount + ", Thất bại: " + failCount);
|
||||
|
||||
} catch (Exception e) {
|
||||
LogHelper.error("Lỗi nghiêm trọng trong job đồng bộ lại dữ liệu doanh thu: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,104 @@
|
|||
package com.vega.hrm.report.serivce;
|
||||
|
||||
import com.vega.hrm.core.entities.RevenueData;
|
||||
import com.vega.hrm.core.helpers.LogHelper;
|
||||
import com.vega.hrm.core.models.responses.PagedList;
|
||||
import com.vega.hrm.core.repositories.RevenueDataRepository;
|
||||
import com.vega.hrm.report.response.RevenueDataDto;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDate;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
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;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class RevenueDataService {
|
||||
|
||||
private final RevenueDataRepository revenueDataRepository;
|
||||
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
|
||||
|
||||
/**
|
||||
* Lưu danh sách dữ liệu doanh thu vào database
|
||||
*/
|
||||
@Transactional
|
||||
public void saveRevenueData(String email, List<RevenueDataDto> revenueDataList) {
|
||||
if (revenueDataList == null || revenueDataList.isEmpty()) {
|
||||
LogHelper.info("Không có dữ liệu doanh thu nào để lưu cho email: " + email);
|
||||
return;
|
||||
}
|
||||
|
||||
int savedCount = 0;
|
||||
int updatedCount = 0;
|
||||
|
||||
for (RevenueDataDto dto : revenueDataList) {
|
||||
try {
|
||||
LocalDate revenueDate = LocalDate.parse(dto.getDate(), DATE_FORMATTER);
|
||||
|
||||
RevenueData existingData = revenueDataRepository.findByEmailAndRevenueDate(email, revenueDate);
|
||||
|
||||
if (existingData != null) {
|
||||
// Cập nhật dữ liệu đã tồn tại
|
||||
existingData.setEstimatedRevenue(dto.getEstimatedRevenue());
|
||||
existingData.setUpdatedAt(Instant.now());
|
||||
revenueDataRepository.save(existingData);
|
||||
updatedCount++;
|
||||
} else {
|
||||
// Tạo mới dữ liệu
|
||||
RevenueData revenueData = new RevenueData();
|
||||
revenueData.setId(UUID.randomUUID());
|
||||
revenueData.setEmail(email);
|
||||
revenueData.setRevenueDate(revenueDate);
|
||||
revenueData.setEstimatedRevenue(dto.getEstimatedRevenue());
|
||||
revenueData.setCreatedAt(Instant.now());
|
||||
revenueData.setUpdatedAt(Instant.now());
|
||||
revenueDataRepository.save(revenueData);
|
||||
savedCount++;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LogHelper.error("Lỗi khi lưu dữ liệu doanh thu cho ngày " + dto.getDate() + ": " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
LogHelper.info("Đã lưu " + savedCount + " bản ghi mới và cập nhật " + updatedCount + " bản ghi cho email: " + email);
|
||||
}
|
||||
|
||||
/**
|
||||
* Lấy dữ liệu doanh thu từ database với phân trang
|
||||
*/
|
||||
public PagedList<RevenueDataDto> getRevenueData(String email, String startDate, String endDate, Pageable pageable) {
|
||||
LocalDate start = LocalDate.parse(startDate, DATE_FORMATTER);
|
||||
LocalDate end = LocalDate.parse(endDate, DATE_FORMATTER);
|
||||
|
||||
Page<RevenueData> page = revenueDataRepository.findByEmailAndDateRange(email, start, end, pageable);
|
||||
|
||||
List<RevenueDataDto> items = page.getContent().stream()
|
||||
.map(this::convertToDto)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
int pageCount = page.getTotalPages();
|
||||
long totalItemCount = page.getTotalElements();
|
||||
|
||||
return PagedList.<RevenueDataDto>builder()
|
||||
.pageCount(pageCount)
|
||||
.totalItemCount(totalItemCount)
|
||||
.items(items)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert entity sang DTO
|
||||
*/
|
||||
private RevenueDataDto convertToDto(RevenueData entity) {
|
||||
return RevenueDataDto.builder()
|
||||
.date(entity.getRevenueDate().format(DATE_FORMATTER))
|
||||
.estimatedRevenue(entity.getEstimatedRevenue())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user