thêm demo report

This commit is contained in:
nguyennt1 2025-11-15 21:25:40 +07:00
parent c04313ec72
commit 2617f55363
17 changed files with 450 additions and 5 deletions

View File

@ -8,6 +8,7 @@
<entry name="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.projectlombok/lombok/1.18.38/57f8f5e02e92a30fd21b80cbd426a4172b5f8e29/lombok-1.18.38.jar" />
</processorPath>
<module name="VegaHRM.Backend.vega-hrm-core.main" />
<module name="VegaHRM.Backend.vega-hrm-report.main" />
<module name="VegaHRM.Backend.vega-hrm-auth.main" />
</profile>
</annotationProcessing>

View File

@ -10,6 +10,7 @@
<option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/vega-hrm-auth" />
<option value="$PROJECT_DIR$/vega-hrm-core" />
<option value="$PROJECT_DIR$/vega-hrm-report" />
</set>
</option>
</GradleProjectSettings>

View File

@ -10,4 +10,5 @@ rootProject.name = 'VegaHRM.Backend'
include 'vega-hrm-auth'
include 'vega-hrm-core'
include 'vega-hrm-report'

View File

@ -11,9 +11,6 @@ repositories {
mavenCentral()
}
dependencies {
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa:3.4.0'
implementation 'org.springframework.boot:spring-boot-starter-web:3.4.0'
@ -24,6 +21,20 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-log4j2:3.4.0'
implementation 'org.springframework.boot:spring-boot-starter-validation:3.4.0'
implementation 'de.mkammerer:argon2-jvm:2.1'
implementation "com.google.apis:google-api-services-youtube:v3-rev182-1.22.0"
implementation("com.google.collections:google-collections:1.0")
implementation("com.google.guava:guava:20.0")
implementation("com.google.apis:google-api-services-youtubeAnalytics:v2-rev272-1.25.0")
implementation "com.google.http-client:google-http-client-jackson2:1.20.0"
// OAuth Client
implementation "com.google.oauth-client:google-oauth-client-jetty:1.20.0"
implementation 'com.google.apis:google-api-services-oauth2:v2-rev157-1.25.0'
implementation 'com.google.oauth-client:google-oauth-client-jetty:1.34.1'
implementation 'com.google.api-client:google-api-client:2.3.0'
// Google Collections
implementation "com.google.collections:google-collections:1.0"
implementation 'com.google.code.gson:gson:2.11.0'
annotationProcessor 'org.projectlombok:lombok:1.18.38'
implementation project(":vega-hrm-core")
implementation 'jakarta.annotation:jakarta.annotation-api:2.1.1'

View File

@ -0,0 +1,29 @@
package com.vega.hrm.controller;
import com.vega.hrm.core.models.responses.BaseResponse;
import com.vega.hrm.service.GoogleService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("api/google/user")
@RequiredArgsConstructor
public class GoogleController {
private final GoogleService googleService;
@GetMapping("/get-auth-url")
public ResponseEntity<BaseResponse<String>> getGoogleAuthUrl(){
return ResponseEntity.ok(googleService.getGoogleAuthUrl());
}
@GetMapping("callback")
public ResponseEntity<BaseResponse<Boolean>> googleCallback(@RequestParam("code") String code){
return ResponseEntity.ok(googleService.googleCallback(code));
}
}

View File

@ -12,7 +12,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/auth/user")
@RequestMapping("api/auth/user")
@RequiredArgsConstructor
public class UserController {
private final UserService userService;

View File

@ -0,0 +1,100 @@
package com.vega.hrm.service;
import static com.vega.hrm.core.constants.CommonConst.SCOPES;
import com.google.api.client.auth.oauth2.AuthorizationCodeFlow;
import com.google.api.client.auth.oauth2.AuthorizationCodeRequestUrl;
import com.google.api.client.auth.oauth2.BearerToken;
import com.google.api.client.auth.oauth2.Credential;
import com.google.api.client.auth.oauth2.TokenResponse;
import com.google.api.client.googleapis.auth.oauth2.GoogleAuthorizationCodeFlow;
import com.google.api.client.googleapis.auth.oauth2.GoogleClientSecrets;
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.google.api.services.oauth2.Oauth2;
import com.vega.hrm.core.component.TokenStore;
import com.vega.hrm.core.models.responses.BaseResponse;
import com.vega.hrm.core.dto.GoogleOAuthConfig;
import com.google.api.services.oauth2.model.Userinfo;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.util.Objects;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
public class GoogleService {
private static final JsonFactory JSON_FACTORY = JacksonFactory.getDefaultInstance();
private final TokenStore tokenStore;
public BaseResponse<String> getGoogleAuthUrl() {
var googleOAuthConfig = GoogleOAuthConfig.builder().build();
NetHttpTransport httpTransport = null;
try {
httpTransport = GoogleNetHttpTransport.newTrustedTransport();
} catch (GeneralSecurityException | IOException e) {
return BaseResponse.invalid(e.getMessage());
}
GoogleClientSecrets.Details details = new GoogleClientSecrets.Details();
details.setClientId(googleOAuthConfig.clientId);
details.setClientSecret(googleOAuthConfig.clientSecret);
GoogleClientSecrets clientSecrets = new GoogleClientSecrets().setInstalled(details);
AuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow.Builder(
httpTransport, JSON_FACTORY, clientSecrets, SCOPES)
.setAccessType("offline")
.setApprovalPrompt("force")
.build();
AuthorizationCodeRequestUrl authorizationUrl = flow.newAuthorizationUrl().setRedirectUri(googleOAuthConfig.redirectUri);
return BaseResponse.success("00",authorizationUrl.toString());
}
public BaseResponse<Boolean> googleCallback(String code) {
var googleOAuthConfig = GoogleOAuthConfig.builder().build();
NetHttpTransport httpTransport = null;
try {
httpTransport = GoogleNetHttpTransport.newTrustedTransport();
} catch (GeneralSecurityException | IOException e) {
BaseResponse.invalid(e.getMessage());
}
GoogleClientSecrets.Details details = new GoogleClientSecrets.Details();
details.setClientId(googleOAuthConfig.clientId);
details.setClientSecret(googleOAuthConfig.clientSecret);
GoogleClientSecrets clientSecrets = new GoogleClientSecrets().setInstalled(details);
AuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow.Builder(
httpTransport, JSON_FACTORY, clientSecrets, SCOPES)
.setAccessType("offline")
.setApprovalPrompt("force")
.build();
TokenResponse tokenResponse = null;
try {
tokenResponse = flow.newTokenRequest(code).setRedirectUri(googleOAuthConfig.redirectUri).execute();
} catch (IOException e) {
return BaseResponse.invalid(e.getMessage());
}
Credential credential = new Credential(BearerToken.authorizationHeaderAccessMethod())
.setAccessToken(tokenResponse.getAccessToken());
Oauth2 oauth2 = new Oauth2.Builder(
Objects.requireNonNull(httpTransport), JSON_FACTORY, credential)
.setApplicationName("VEGA_HRM")
.build();
Userinfo userInfo = null;
try {
userInfo = oauth2.userinfo().get().execute();
} catch (IOException e) {
return BaseResponse.invalid(e.getMessage());
}
String email = userInfo.getEmail();
tokenStore.storeToken(email, tokenResponse);
return BaseResponse.success("00",true);
}
}

View File

@ -44,6 +44,19 @@ dependencies {
implementation 'org.bouncycastle:bcpkix-jdk18on:1.80'
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.12.6'
implementation "com.google.apis:google-api-services-youtube:v3-rev182-1.22.0"
implementation("com.google.collections:google-collections:1.0")
implementation("com.google.guava:guava:20.0")
implementation("com.google.apis:google-api-services-youtubeAnalytics:v2-rev272-1.25.0")
implementation "com.google.http-client:google-http-client-jackson2:1.20.0"
// OAuth Client
implementation "com.google.oauth-client:google-oauth-client-jetty:1.20.0"
// Google Collections
implementation "com.google.collections:google-collections:1.0"
implementation 'com.google.code.gson:gson:2.11.0'
// https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt-orgjson
runtimeOnly 'io.jsonwebtoken:jjwt-orgjson:0.12.6'
}

View File

@ -0,0 +1,36 @@
package com.vega.hrm.core.component;
import com.google.api.client.auth.oauth2.Credential;
import com.google.api.client.auth.oauth2.TokenResponse;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.json.JsonFactory;
import java.util.concurrent.ConcurrentHashMap;
import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;
import org.springframework.stereotype.Component;
@Component
public class TokenStore {
private final ConcurrentHashMap<String, TokenResponse> tokenMap = new ConcurrentHashMap<>();
public void storeToken(String userId, TokenResponse tokenResponse) {
tokenMap.put(userId, tokenResponse);
}
public TokenResponse getTokenResponse(String userId) {
return tokenMap.get(userId);
}
public Credential buildCredential(HttpTransport transport, JsonFactory jsonFactory, String userId, String clientId, String clientSecret) {
TokenResponse tokenResponse = tokenMap.get(userId);
if (tokenResponse == null) {
return null;
}
return new GoogleCredential.Builder()
.setTransport(transport)
.setJsonFactory(jsonFactory)
.setClientSecrets(clientId, clientSecret)
.build()
.setFromTokenResponse(tokenResponse);
}
}

View File

@ -1,6 +1,14 @@
package com.vega.hrm.core.constants;
import java.util.Arrays;
import java.util.List;
public class CommonConst {
public static final String USER_ID = "user_id";
public static final String TOKEN = "token";
public static final List<String> SCOPES = Arrays.asList(
"https://www.googleapis.com/auth/youtube.readonly",
"https://www.googleapis.com/auth/yt-analytics.readonly",
"https://www.googleapis.com/auth/yt-analytics-monetary.readonly"
);
}

View File

@ -0,0 +1,14 @@
package com.vega.hrm.core.dto;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
@Builder
public class GoogleOAuthConfig {
public String clientId;
public String clientSecret;
public String redirectUri;
}

View File

@ -28,7 +28,7 @@ import org.springframework.web.filter.OncePerRequestFilter;
@RequiredArgsConstructor
public class AuthorizationFilter extends OncePerRequestFilter {
private static final List<String> EXCLUDE_URIS = List.of("/auth/user/login");
private static final List<String> EXCLUDE_URIS = List.of("api/auth/user/login","api/google/user/callback");
private final RedisService redisService;
@Override

View File

@ -28,6 +28,14 @@ public class BaseResponse<T> {
private T data;
public static <T> BaseResponse<T> success(String msg, T data) {
return BaseResponse.<T>builder()
.code(ResponseCodeConst.SUCCESS)
.message(msg)
.data(data)
.build();
}
public static <T> BaseResponse<T> success(String msg) {
return BaseResponse.<T>builder()
.code(ResponseCodeConst.SUCCESS)

View File

@ -0,0 +1,47 @@
package com.vega.hrm.report.controller;
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.dto.GoogleOAuthConfig;
import com.vega.hrm.core.models.responses.BaseResponse;
import com.vega.hrm.report.request.GetDragRevenueRequest;
import com.vega.hrm.report.serivce.CreateReportingJobService;
import java.io.IOException;
import java.security.GeneralSecurityException;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("api/report/google")
@RequiredArgsConstructor
public class ReportGoogleController {
private final CreateReportingJobService createReportingJob;
@GetMapping("/youtube/demo")
public ResponseEntity<BaseResponse<Boolean>> getRevenue(GetDragRevenueRequest getDragRevenueRequest)
throws GeneralSecurityException, IOException {
var googleOAuthConfig = GoogleOAuthConfig.builder().build();
var tokenStore = new TokenStore();
createReportingJob.createJobWithStoredCredential(
tokenStore,
googleOAuthConfig,
GoogleNetHttpTransport.newTrustedTransport(),
JacksonFactory.getDefaultInstance(),
"default",
"channel_monetized_playback_a1", // report type
"vega-monetization-daily" // job name
);
return ResponseEntity.ok(
BaseResponse.<Boolean>builder()
.code("00")
.data(true)
.build()
);
}
}

View File

@ -0,0 +1,8 @@
package com.vega.hrm.report.request;
import lombok.Getter;
@Getter
public class GetDragRevenueRequest {
public String email;
}

View File

@ -0,0 +1,159 @@
package com.vega.hrm.report.serivce;
import com.google.api.client.auth.oauth2.Credential;
import com.google.api.client.googleapis.json.GoogleJsonResponseException;
import com.google.api.client.http.GenericUrl;
import com.google.api.services.youtubeAnalytics.v2.YouTubeAnalytics;
import com.google.api.services.youtubeAnalytics.v2.model.QueryResponse;
import com.google.api.services.youtubereporting.YouTubeReporting;
import com.google.api.services.youtubereporting.YouTubeReporting.Media.Download;
import com.google.api.services.youtubereporting.model.Job;
import com.google.api.services.youtubereporting.model.ListReportTypesResponse;
import com.google.api.services.youtubereporting.model.ListReportsResponse;
import com.google.api.services.youtubereporting.model.Report;
import com.google.api.services.youtubereporting.model.ReportType;
import com.vega.hrm.core.component.TokenStore;
import com.vega.hrm.core.dto.GoogleOAuthConfig;
import com.vega.hrm.core.helpers.LogHelper;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
public class CreateReportingJobService {
private static YouTubeReporting youtubeReporting;
private static final String APPLICATION_NAME = "vega-report";
public void createJobWithStoredCredential(
TokenStore tokenStore,
GoogleOAuthConfig googleOAuthConfig,
com.google.api.client.http.HttpTransport httpTransport,
com.google.api.client.json.JsonFactory jsonFactory,
String userId,
String reportTypeId,
String jobName
) {
try {
Credential credential = tokenStore.buildCredential(
httpTransport,
jsonFactory,
userId,
googleOAuthConfig.clientId,
googleOAuthConfig.clientSecret
);
if (credential == null) {
throw new IllegalStateException("No stored token for userId=" + userId);
}
youtubeReporting = new YouTubeReporting.Builder(httpTransport, jsonFactory, credential)
.setApplicationName(APPLICATION_NAME)
.build();
YouTubeAnalytics youtubeAnalytics = new YouTubeAnalytics.Builder(httpTransport, jsonFactory, credential)
.setApplicationName(APPLICATION_NAME)
.build();
QueryResponse resReport = youtubeAnalytics.reports().query().setIds("channel==MINE")
.setStartDate("2025-01-01")
.setEndDate("2025-09-30")
.setMetrics("estimatedRevenue")
.setDimensions("day").execute();
LogHelper.info("resReport: " + resReport.toPrettyString());
//TODO tam fix cứng
if (listReportTypes()) {
createReportingJob(reportTypeId, "test_job");
}
} catch (GoogleJsonResponseException e) {
LogHelper.error("GoogleJsonResponseException code: " + e.getDetails().getCode()
+ " : " + e.getDetails().getMessage());
} catch (IOException e) {
LogHelper.error("IOException: " + e.getMessage());
} catch (Throwable t) {
LogHelper.error("Throwable: " + t.getMessage());
}
}
private static boolean listReportTypes() throws IOException {
ListReportTypesResponse reportTypesListResponse = youtubeReporting.reportTypes().list()
.execute();
List<ReportType> reportTypeList = reportTypesListResponse.getReportTypes();
if (reportTypeList == null || reportTypeList.isEmpty()) {
LogHelper.info("No report types found.");
return false;
} else {
// Print information from the API response.
LogHelper.info("\n================== Report Types ==================\n");
for (ReportType reportType : reportTypeList) {
LogHelper.info(" - Id: " + reportType.getId());
LogHelper.info(" - Name: " + reportType.getName());
LogHelper.info("\n-------------------------------------------------------------\n");
}
}
return true;
}
private static boolean retrieveReports(String jobId)
throws IOException {
ListReportsResponse reportsListResponse = youtubeReporting.jobs().reports().list(jobId).execute();
List<Report> reportslist = reportsListResponse.getReports();
if (reportslist == null || reportslist.isEmpty()) {
LogHelper.info("No reports found.");
return false;
} else {
LogHelper.info("\n============= Reports for the job " + jobId + " =============\n");
for (Report report : reportslist) {
LogHelper.info(" - Id: " + report.getId());
LogHelper.info(" - From: " + report.getStartTime());
LogHelper.info(" - To: " + report.getEndTime());
LogHelper.info(" - Download Url: " + report.getDownloadUrl());
downloadReport(report.getDownloadUrl());
LogHelper.info("\n-------------------------------------------------------------\n");
}
}
return true;
}
private static boolean downloadReport(String reportUrl) throws IOException {
// Tạo request download
Download request = youtubeReporting.media().download(
String.valueOf(new GenericUrl(reportUrl)));
// Tạo output file
try (FileOutputStream fop = new FileOutputStream(new File("report.csv"))) {
request.getMediaHttpDownloader().download(new GenericUrl(reportUrl), fop);
}
return true;
}
private static void createReportingJob(String reportTypeId, String name)
throws IOException {
Job job = new Job();
job.setReportTypeId(reportTypeId);
job.setName(name);
Job createdJob = youtubeReporting.jobs().create(job).execute();
retrieveReports(createdJob.getId());
LogHelper.info("\n================== Created reporting job ==================\n");
LogHelper.info(" - ID: " + createdJob.getId());
LogHelper.info(" - Name: " + createdJob.getName());
LogHelper.info(" - Report Type Id: " + createdJob.getReportTypeId());
LogHelper.info(" - Create Time: " + createdJob.getCreateTime());
LogHelper.info("\n-------------------------------------------------------------\n");
}
}

View File

@ -0,0 +1,9 @@
spring.application.name=vega-hrm-report
spring.jpa.open-in-view=false
server.port=8090
vega.hrm.postgre.enabled=true
vega.jpa.repository.basePackage=com.vega.hrm.core.repositories
vega.jpa.entity.basePackage=com.vega.hrm.core.entities
spring.config.import=file:config/shared.properties
logging.config=file:config/log4j2.properties