parser is ready
This commit is contained in:
@@ -1,15 +1,20 @@
|
||||
package kz.konturai.parser.controller;
|
||||
|
||||
import kz.konturai.parser.dto.*;
|
||||
import kz.konturai.parser.model.MarketItem;
|
||||
import kz.konturai.parser.service.KursivParserService;
|
||||
import kz.konturai.parser.service.KapitalParserService;
|
||||
import kz.konturai.parser.service.MarketItemService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.web.PageableDefault;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/parser")
|
||||
@@ -22,31 +27,29 @@ public class ParserController {
|
||||
@Autowired
|
||||
private KapitalParserService kapitalParserService;
|
||||
|
||||
@Autowired
|
||||
private MarketItemService marketItemService;
|
||||
|
||||
/**
|
||||
* Запуск парсинга RSS-ленты Kursiv
|
||||
*/
|
||||
@PostMapping("/parse/kursiv")
|
||||
public ResponseEntity<Map<String, Object>> parseKursivRssFeed() {
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
|
||||
public ResponseEntity<ApiResponse<ParserResultDto>> parseKursivRssFeed() {
|
||||
try {
|
||||
List<MarketItem> savedItems = kursivParserService.parseAndSaveRssFeed();
|
||||
long totalItems = marketItemService.getTotalItemsCount();
|
||||
|
||||
response.put("success", true);
|
||||
response.put("message", "Парсинг Kursiv завершен успешно");
|
||||
response.put("source", "Kursiv");
|
||||
response.put("processedItems", savedItems.size());
|
||||
response.put("totalItemsInDb", kursivParserService.getTotalItemsCount());
|
||||
ParserResultDto result = new ParserResultDto(
|
||||
"Kursiv",
|
||||
savedItems.size(),
|
||||
totalItems,
|
||||
"completed");
|
||||
|
||||
return ResponseEntity.ok(response);
|
||||
return ResponseEntity.ok(ApiResponse.success("Парсинг Kursiv завершен успешно", result));
|
||||
|
||||
} catch (Exception e) {
|
||||
response.put("success", false);
|
||||
response.put("message", "Ошибка при парсинге Kursiv: " + e.getMessage());
|
||||
response.put("source", "Kursiv");
|
||||
response.put("processedItems", 0);
|
||||
|
||||
return ResponseEntity.internalServerError().body(response);
|
||||
return ResponseEntity.internalServerError()
|
||||
.body(ApiResponse.error("Ошибка при парсинге Kursiv: " + e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,27 +57,22 @@ public class ParserController {
|
||||
* Запуск парсинга RSS-ленты Kapital
|
||||
*/
|
||||
@PostMapping("/parse/kapital")
|
||||
public ResponseEntity<Map<String, Object>> parseKapitalRssFeed() {
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
|
||||
public ResponseEntity<ApiResponse<ParserResultDto>> parseKapitalRssFeed() {
|
||||
try {
|
||||
List<MarketItem> savedItems = kapitalParserService.parseAndSaveRssFeed();
|
||||
long totalItems = marketItemService.getTotalItemsCount();
|
||||
|
||||
response.put("success", true);
|
||||
response.put("message", "Парсинг Kapital завершен успешно");
|
||||
response.put("source", "Kapital");
|
||||
response.put("processedItems", savedItems.size());
|
||||
response.put("totalItemsInDb", kapitalParserService.getKapitalItemsCount());
|
||||
ParserResultDto result = new ParserResultDto(
|
||||
"Kapital",
|
||||
savedItems.size(),
|
||||
totalItems,
|
||||
"completed");
|
||||
|
||||
return ResponseEntity.ok(response);
|
||||
return ResponseEntity.ok(ApiResponse.success("Парсинг Kapital завершен успешно", result));
|
||||
|
||||
} catch (Exception e) {
|
||||
response.put("success", false);
|
||||
response.put("message", "Ошибка при парсинге Kapital: " + e.getMessage());
|
||||
response.put("source", "Kapital");
|
||||
response.put("processedItems", 0);
|
||||
|
||||
return ResponseEntity.internalServerError().body(response);
|
||||
return ResponseEntity.internalServerError()
|
||||
.body(ApiResponse.error("Ошибка при парсинге Kapital: " + e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,30 +80,63 @@ public class ParserController {
|
||||
* Запуск парсинга всех RSS-лент
|
||||
*/
|
||||
@PostMapping("/parse/all")
|
||||
public ResponseEntity<Map<String, Object>> parseAllRssFeeds() {
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
|
||||
public ResponseEntity<ApiResponse<List<ParserResultDto>>> parseAllRssFeeds() {
|
||||
try {
|
||||
List<MarketItem> kursivItems = kursivParserService.parseAndSaveRssFeed();
|
||||
List<MarketItem> kapitalItems = kapitalParserService.parseAndSaveRssFeed();
|
||||
long totalItems = marketItemService.getTotalItemsCount();
|
||||
|
||||
int totalProcessed = kursivItems.size() + kapitalItems.size();
|
||||
ParserResultDto kursivResult = new ParserResultDto(
|
||||
"Kursiv",
|
||||
kursivItems.size(),
|
||||
totalItems,
|
||||
"completed");
|
||||
|
||||
response.put("success", true);
|
||||
response.put("message", "Парсинг всех источников завершен успешно");
|
||||
response.put("kursivProcessed", kursivItems.size());
|
||||
response.put("kapitalProcessed", kapitalItems.size());
|
||||
response.put("totalProcessed", totalProcessed);
|
||||
response.put("totalItemsInDb", kursivParserService.getTotalItemsCount());
|
||||
ParserResultDto kapitalResult = new ParserResultDto(
|
||||
"Kapital",
|
||||
kapitalItems.size(),
|
||||
totalItems,
|
||||
"completed");
|
||||
|
||||
return ResponseEntity.ok(response);
|
||||
List<ParserResultDto> results = List.of(kursivResult, kapitalResult);
|
||||
|
||||
return ResponseEntity.ok(ApiResponse.success("Парсинг всех источников завершен успешно", results));
|
||||
|
||||
} catch (Exception e) {
|
||||
response.put("success", false);
|
||||
response.put("message", "Ошибка при парсинге всех источников: " + e.getMessage());
|
||||
response.put("totalProcessed", 0);
|
||||
return ResponseEntity.internalServerError()
|
||||
.body(ApiResponse.error("Ошибка при парсинге всех источников: " + e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
return ResponseEntity.internalServerError().body(response);
|
||||
/**
|
||||
* Получение записей с фильтрацией и пагинацией
|
||||
*/
|
||||
@GetMapping("/items")
|
||||
public ResponseEntity<ApiResponse<Page<MarketItem>>> getItems(
|
||||
@RequestParam(required = false) String sourceName,
|
||||
@RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) String startDate,
|
||||
@RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) String endDate,
|
||||
@PageableDefault(size = 20, sort = "publishedAt", direction = org.springframework.data.domain.Sort.Direction.DESC) Pageable pageable) {
|
||||
|
||||
try {
|
||||
LocalDateTime start = marketItemService.parseDate(startDate);
|
||||
LocalDateTime end = marketItemService.parseDate(endDate);
|
||||
|
||||
Page<MarketItem> items;
|
||||
|
||||
if (sourceName != null || start != null || end != null) {
|
||||
// Используем фильтрацию
|
||||
items = marketItemService.getItemsWithFilters(sourceName, start, end, pageable);
|
||||
} else {
|
||||
// Получаем все записи
|
||||
items = marketItemService.getAllItems(pageable);
|
||||
}
|
||||
|
||||
return ResponseEntity.ok(ApiResponse.success("Записи получены успешно", items));
|
||||
|
||||
} catch (Exception e) {
|
||||
return ResponseEntity.internalServerError()
|
||||
.body(ApiResponse.error("Ошибка при получении записей: " + e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,51 +144,19 @@ public class ParserController {
|
||||
* Получение статистики по сохраненным записям
|
||||
*/
|
||||
@GetMapping("/stats")
|
||||
public ResponseEntity<Map<String, Object>> getStats() {
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
|
||||
public ResponseEntity<ApiResponse<ParserStatsDto>> getStats() {
|
||||
try {
|
||||
long totalCount = kursivParserService.getTotalItemsCount();
|
||||
long totalCount = marketItemService.getTotalItemsCount();
|
||||
var sourceStatistics = marketItemService.getSourceStatistics();
|
||||
String lastUpdate = java.time.LocalDateTime.now().toString();
|
||||
|
||||
response.put("success", true);
|
||||
response.put("totalItems", totalCount);
|
||||
response.put("message", "Статистика получена успешно");
|
||||
ParserStatsDto stats = new ParserStatsDto(totalCount, sourceStatistics, lastUpdate);
|
||||
|
||||
return ResponseEntity.ok(response);
|
||||
return ResponseEntity.ok(ApiResponse.success("Статистика получена успешно", stats));
|
||||
|
||||
} catch (Exception e) {
|
||||
response.put("success", false);
|
||||
response.put("message", "Ошибка при получении статистики: " + e.getMessage());
|
||||
response.put("totalItems", 0);
|
||||
|
||||
return ResponseEntity.internalServerError().body(response);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Получение всех сохраненных записей
|
||||
*/
|
||||
@GetMapping("/items")
|
||||
public ResponseEntity<Map<String, Object>> getAllItems() {
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
|
||||
try {
|
||||
List<MarketItem> items = kursivParserService.getAllItems();
|
||||
|
||||
response.put("success", true);
|
||||
response.put("items", items);
|
||||
response.put("count", items.size());
|
||||
response.put("message", "Записи получены успешно");
|
||||
|
||||
return ResponseEntity.ok(response);
|
||||
|
||||
} catch (Exception e) {
|
||||
response.put("success", false);
|
||||
response.put("message", "Ошибка при получении записей: " + e.getMessage());
|
||||
response.put("items", List.of());
|
||||
response.put("count", 0);
|
||||
|
||||
return ResponseEntity.internalServerError().body(response);
|
||||
return ResponseEntity.internalServerError()
|
||||
.body(ApiResponse.error("Ошибка при получении статистики: " + e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -165,21 +164,14 @@ public class ParserController {
|
||||
* Получение записи по ID
|
||||
*/
|
||||
@GetMapping("/items/{id}")
|
||||
public ResponseEntity<Map<String, Object>> getItemById(@PathVariable String id) {
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
|
||||
public ResponseEntity<ApiResponse<MarketItem>> getItemById(@PathVariable String id) {
|
||||
try {
|
||||
// Здесь можно добавить метод для поиска по ID в сервисе
|
||||
response.put("success", false);
|
||||
response.put("message", "Функция поиска по ID пока не реализована");
|
||||
|
||||
return ResponseEntity.ok(response);
|
||||
return ResponseEntity.ok(ApiResponse.error("Функция поиска по ID пока не реализована"));
|
||||
|
||||
} catch (Exception e) {
|
||||
response.put("success", false);
|
||||
response.put("message", "Ошибка при получении записи: " + e.getMessage());
|
||||
|
||||
return ResponseEntity.internalServerError().body(response);
|
||||
return ResponseEntity.internalServerError()
|
||||
.body(ApiResponse.error("Ошибка при получении записи: " + e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -187,31 +179,60 @@ public class ParserController {
|
||||
* Проверка состояния парсера
|
||||
*/
|
||||
@GetMapping("/health")
|
||||
public ResponseEntity<Map<String, Object>> healthCheck() {
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
public ResponseEntity<ApiResponse<HealthCheckDto>> healthCheck() {
|
||||
HealthCheckDto health = new HealthCheckDto(
|
||||
"UP",
|
||||
"RSS Parser System",
|
||||
System.currentTimeMillis(),
|
||||
"Enabled - runs every 30 minutes");
|
||||
|
||||
response.put("status", "UP");
|
||||
response.put("service", "Kursiv RSS Parser");
|
||||
response.put("timestamp", System.currentTimeMillis());
|
||||
response.put("scheduler", "Enabled - runs every 30 minutes");
|
||||
return ResponseEntity.ok(ApiResponse.success("Сервис работает", health));
|
||||
}
|
||||
|
||||
return ResponseEntity.ok(response);
|
||||
/**
|
||||
* Проверка подключения к MongoDB
|
||||
*/
|
||||
@GetMapping("/health/mongodb")
|
||||
public ResponseEntity<ApiResponse<HealthCheckDto>> checkMongoConnection() {
|
||||
try {
|
||||
long count = marketItemService.getTotalItemsCount();
|
||||
HealthCheckDto health = new HealthCheckDto(
|
||||
"UP",
|
||||
"MongoDB подключение успешно",
|
||||
System.currentTimeMillis(),
|
||||
null);
|
||||
|
||||
return ResponseEntity.ok(ApiResponse.success("MongoDB подключение успешно", health));
|
||||
|
||||
} catch (Exception e) {
|
||||
HealthCheckDto health = new HealthCheckDto(
|
||||
"DOWN",
|
||||
"Ошибка подключения к MongoDB: " + e.getMessage(),
|
||||
System.currentTimeMillis(),
|
||||
null);
|
||||
|
||||
if (e.getMessage().contains("Unauthorized") || e.getMessage().contains("authentication")) {
|
||||
health.setSuggestion("Проверьте учетные данные в application.properties");
|
||||
}
|
||||
|
||||
return ResponseEntity.status(503).body(ApiResponse.error("Ошибка подключения к MongoDB", health));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Получение информации о планировщике
|
||||
*/
|
||||
@GetMapping("/scheduler/info")
|
||||
public ResponseEntity<Map<String, Object>> getSchedulerInfo() {
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
public ResponseEntity<ApiResponse<java.util.Map<String, Object>>> getSchedulerInfo() {
|
||||
java.util.Map<String, Object> schedulerInfo = new java.util.HashMap<>();
|
||||
|
||||
response.put("schedulerEnabled", true);
|
||||
response.put("cronExpression", "0 0/30 * * * ?");
|
||||
response.put("description", "Запуск каждые 30 минут (в 0 и 30 минут каждого часа)");
|
||||
response.put("nextRun", "Следующий запуск будет в ближайшие 0 или 30 минут часа");
|
||||
response.put("activeParsers", List.of("KursivParserService", "KapitalParserService"));
|
||||
response.put("sources", List.of("Kursiv Media", "Kapital.kz"));
|
||||
schedulerInfo.put("schedulerEnabled", true);
|
||||
schedulerInfo.put("cronExpression", "0 0/30 * * * ?");
|
||||
schedulerInfo.put("description", "Запуск каждые 30 минут (в 0 и 30 минут каждого часа)");
|
||||
schedulerInfo.put("nextRun", "Следующий запуск будет в ближайшие 0 или 30 минут часа");
|
||||
schedulerInfo.put("activeParsers", List.of("KursivParserService", "KapitalParserService"));
|
||||
schedulerInfo.put("sources", List.of("Kursiv Media", "Kapital.kz"));
|
||||
|
||||
return ResponseEntity.ok(response);
|
||||
return ResponseEntity.ok(ApiResponse.success("Информация о планировщике получена", schedulerInfo));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
package kz.konturai.parser.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
public class ApiResponse<T> {
|
||||
|
||||
private boolean success;
|
||||
private String message;
|
||||
private T data;
|
||||
private String error;
|
||||
|
||||
public ApiResponse() {
|
||||
}
|
||||
|
||||
public ApiResponse(boolean success, String message, T data) {
|
||||
this.success = success;
|
||||
this.message = message;
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public ApiResponse(boolean success, String message, String error) {
|
||||
this.success = success;
|
||||
this.message = message;
|
||||
this.error = error;
|
||||
}
|
||||
|
||||
public static <T> ApiResponse<T> success(T data) {
|
||||
return new ApiResponse<>(true, "Операция выполнена успешно", data);
|
||||
}
|
||||
|
||||
public static <T> ApiResponse<T> success(String message, T data) {
|
||||
return new ApiResponse<>(true, message, data);
|
||||
}
|
||||
|
||||
public static <T> ApiResponse<T> error(String message) {
|
||||
return new ApiResponse<>(false, message, message);
|
||||
}
|
||||
|
||||
public static <T> ApiResponse<T> error(String message, String error) {
|
||||
return new ApiResponse<>(false, message, error);
|
||||
}
|
||||
|
||||
public static <T> ApiResponse<T> error(String message, T data) {
|
||||
ApiResponse<T> response = new ApiResponse<>(false, message, data);
|
||||
return response;
|
||||
}
|
||||
|
||||
// Getters and Setters
|
||||
public boolean isSuccess() {
|
||||
return success;
|
||||
}
|
||||
|
||||
public void setSuccess(boolean success) {
|
||||
this.success = success;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public void setMessage(String message) {
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public T getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
public void setData(T data) {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public String getError() {
|
||||
return error;
|
||||
}
|
||||
|
||||
public void setError(String error) {
|
||||
this.error = error;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package kz.konturai.parser.dto;
|
||||
|
||||
public class HealthCheckDto {
|
||||
|
||||
private String status;
|
||||
private String service;
|
||||
private long timestamp;
|
||||
private String scheduler;
|
||||
private String message;
|
||||
private String suggestion;
|
||||
|
||||
public HealthCheckDto() {
|
||||
}
|
||||
|
||||
public HealthCheckDto(String status, String service, long timestamp, String scheduler) {
|
||||
this.status = status;
|
||||
this.service = service;
|
||||
this.timestamp = timestamp;
|
||||
this.scheduler = scheduler;
|
||||
}
|
||||
|
||||
public HealthCheckDto(String status, String message, long timestamp) {
|
||||
this.status = status;
|
||||
this.message = message;
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
// Getters and Setters
|
||||
public String getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setStatus(String status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public String getService() {
|
||||
return service;
|
||||
}
|
||||
|
||||
public void setService(String service) {
|
||||
this.service = service;
|
||||
}
|
||||
|
||||
public long getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
public void setTimestamp(long timestamp) {
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
public String getScheduler() {
|
||||
return scheduler;
|
||||
}
|
||||
|
||||
public void setScheduler(String scheduler) {
|
||||
this.scheduler = scheduler;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public void setMessage(String message) {
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public String getSuggestion() {
|
||||
return suggestion;
|
||||
}
|
||||
|
||||
public void setSuggestion(String suggestion) {
|
||||
this.suggestion = suggestion;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package kz.konturai.parser.dto;
|
||||
|
||||
public class ParserResultDto {
|
||||
|
||||
private String source;
|
||||
private int processedItems;
|
||||
private long totalItemsInDb;
|
||||
private String status;
|
||||
|
||||
public ParserResultDto() {
|
||||
}
|
||||
|
||||
public ParserResultDto(String source, int processedItems, long totalItemsInDb, String status) {
|
||||
this.source = source;
|
||||
this.processedItems = processedItems;
|
||||
this.totalItemsInDb = totalItemsInDb;
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
// Getters and Setters
|
||||
public String getSource() {
|
||||
return source;
|
||||
}
|
||||
|
||||
public void setSource(String source) {
|
||||
this.source = source;
|
||||
}
|
||||
|
||||
public int getProcessedItems() {
|
||||
return processedItems;
|
||||
}
|
||||
|
||||
public void setProcessedItems(int processedItems) {
|
||||
this.processedItems = processedItems;
|
||||
}
|
||||
|
||||
public long getTotalItemsInDb() {
|
||||
return totalItemsInDb;
|
||||
}
|
||||
|
||||
public void setTotalItemsInDb(long totalItemsInDb) {
|
||||
this.totalItemsInDb = totalItemsInDb;
|
||||
}
|
||||
|
||||
public String getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setStatus(String status) {
|
||||
this.status = status;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package kz.konturai.parser.dto;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public class ParserStatsDto {
|
||||
|
||||
private long totalItems;
|
||||
private Map<String, Long> sourceStatistics;
|
||||
private String lastUpdate;
|
||||
|
||||
public ParserStatsDto() {
|
||||
}
|
||||
|
||||
public ParserStatsDto(long totalItems, Map<String, Long> sourceStatistics, String lastUpdate) {
|
||||
this.totalItems = totalItems;
|
||||
this.sourceStatistics = sourceStatistics;
|
||||
this.lastUpdate = lastUpdate;
|
||||
}
|
||||
|
||||
// Getters and Setters
|
||||
public long getTotalItems() {
|
||||
return totalItems;
|
||||
}
|
||||
|
||||
public void setTotalItems(long totalItems) {
|
||||
this.totalItems = totalItems;
|
||||
}
|
||||
|
||||
public Map<String, Long> getSourceStatistics() {
|
||||
return sourceStatistics;
|
||||
}
|
||||
|
||||
public void setSourceStatistics(Map<String, Long> sourceStatistics) {
|
||||
this.sourceStatistics = sourceStatistics;
|
||||
}
|
||||
|
||||
public String getLastUpdate() {
|
||||
return lastUpdate;
|
||||
}
|
||||
|
||||
public void setLastUpdate(String lastUpdate) {
|
||||
this.lastUpdate = lastUpdate;
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,14 @@
|
||||
package kz.konturai.parser.repository;
|
||||
|
||||
import kz.konturai.parser.model.MarketItem;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.mongodb.repository.MongoRepository;
|
||||
import org.springframework.data.mongodb.repository.Query;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@Repository
|
||||
@@ -18,4 +23,33 @@ public interface MarketItemRepository extends MongoRepository<MarketItem, String
|
||||
* Проверить существование записи по хэшу
|
||||
*/
|
||||
boolean existsByHash(String hash);
|
||||
|
||||
/**
|
||||
* Найти записи по источнику с пагинацией
|
||||
*/
|
||||
Page<MarketItem> findBySourceName(String sourceName, Pageable pageable);
|
||||
|
||||
/**
|
||||
* Найти записи по диапазону дат с пагинацией
|
||||
*/
|
||||
Page<MarketItem> findByPublishedAtBetween(LocalDateTime startDate, LocalDateTime endDate, Pageable pageable);
|
||||
|
||||
/**
|
||||
* Подсчитать записи по источнику
|
||||
*/
|
||||
long countBySourceName(String sourceName);
|
||||
|
||||
/**
|
||||
* Получить все уникальные названия источников
|
||||
*/
|
||||
@Query(value = "{}", fields = "{'sourceName': 1}")
|
||||
List<String> findDistinctSourceNames();
|
||||
|
||||
/**
|
||||
* Найти записи по источнику и диапазону дат
|
||||
*/
|
||||
Page<MarketItem> findBySourceNameAndPublishedAtBetween(String sourceName,
|
||||
LocalDateTime startDate,
|
||||
LocalDateTime endDate,
|
||||
Pageable pageable);
|
||||
}
|
||||
|
||||
@@ -242,6 +242,13 @@ public class KapitalParserService {
|
||||
return savedItem;
|
||||
}
|
||||
|
||||
} catch (org.springframework.data.mongodb.UncategorizedMongoDbException e) {
|
||||
if (e.getMessage().contains("Unauthorized") || e.getMessage().contains("authentication")) {
|
||||
logger.error("❌ Ошибка аутентификации MongoDB. Проверьте учетные данные в application.properties");
|
||||
} else {
|
||||
logger.error("❌ Ошибка подключения к MongoDB: {}", e.getMessage());
|
||||
}
|
||||
return null;
|
||||
} catch (Exception e) {
|
||||
logger.error("Ошибка при сохранении записи Kapital.kz: {}", marketItem.getTitle(), e);
|
||||
return null;
|
||||
|
||||
@@ -238,6 +238,13 @@ public class KursivParserService {
|
||||
return savedItem;
|
||||
}
|
||||
|
||||
} catch (org.springframework.data.mongodb.UncategorizedMongoDbException e) {
|
||||
if (e.getMessage().contains("Unauthorized") || e.getMessage().contains("authentication")) {
|
||||
logger.error("❌ Ошибка аутентификации MongoDB. Проверьте учетные данные в application.properties");
|
||||
} else {
|
||||
logger.error("❌ Ошибка подключения к MongoDB: {}", e.getMessage());
|
||||
}
|
||||
return null;
|
||||
} catch (Exception e) {
|
||||
logger.error("Ошибка при сохранении записи: {}", marketItem.getTitle(), e);
|
||||
return null;
|
||||
|
||||
@@ -0,0 +1,166 @@
|
||||
package kz.konturai.parser.service;
|
||||
|
||||
import kz.konturai.parser.model.MarketItem;
|
||||
import kz.konturai.parser.repository.MarketItemRepository;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.mongodb.core.MongoTemplate;
|
||||
import org.springframework.data.mongodb.core.query.Criteria;
|
||||
import org.springframework.data.mongodb.core.query.Query;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
public class MarketItemService {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(MarketItemService.class);
|
||||
|
||||
@Autowired
|
||||
private MarketItemRepository marketItemRepository;
|
||||
|
||||
@Autowired
|
||||
private MongoTemplate mongoTemplate;
|
||||
|
||||
/**
|
||||
* Получение всех записей с пагинацией и сортировкой
|
||||
*/
|
||||
public Page<MarketItem> getAllItems(Pageable pageable) {
|
||||
logger.debug("Получение всех записей с пагинацией: page={}, size={}, sort={}",
|
||||
pageable.getPageNumber(), pageable.getPageSize(), pageable.getSort());
|
||||
|
||||
return marketItemRepository.findAll(pageable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Получение записей с фильтрацией по источнику
|
||||
*/
|
||||
public Page<MarketItem> getItemsBySource(String sourceName, Pageable pageable) {
|
||||
logger.debug("Получение записей по источнику: sourceName={}, page={}, size={}",
|
||||
sourceName, pageable.getPageNumber(), pageable.getPageSize());
|
||||
|
||||
return marketItemRepository.findBySourceName(sourceName, pageable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Получение записей с фильтрацией по диапазону дат
|
||||
*/
|
||||
public Page<MarketItem> getItemsByDateRange(LocalDateTime startDate, LocalDateTime endDate, Pageable pageable) {
|
||||
logger.debug("Получение записей по диапазону дат: startDate={}, endDate={}, page={}, size={}",
|
||||
startDate, endDate, pageable.getPageNumber(), pageable.getPageSize());
|
||||
|
||||
return marketItemRepository.findByPublishedAtBetween(startDate, endDate, pageable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Получение записей с комплексной фильтрацией
|
||||
*/
|
||||
public Page<MarketItem> getItemsWithFilters(String sourceName, LocalDateTime startDate,
|
||||
LocalDateTime endDate, Pageable pageable) {
|
||||
logger.debug("Получение записей с фильтрацией: sourceName={}, startDate={}, endDate={}, page={}, size={}",
|
||||
sourceName, startDate, endDate, pageable.getPageNumber(), pageable.getPageSize());
|
||||
|
||||
Query query = new Query();
|
||||
|
||||
// Фильтр по источнику
|
||||
if (sourceName != null && !sourceName.trim().isEmpty()) {
|
||||
query.addCriteria(Criteria.where("sourceName").is(sourceName));
|
||||
}
|
||||
|
||||
// Фильтр по диапазону дат
|
||||
if (startDate != null && endDate != null) {
|
||||
query.addCriteria(Criteria.where("publishedAt").gte(startDate).lte(endDate));
|
||||
} else if (startDate != null) {
|
||||
query.addCriteria(Criteria.where("publishedAt").gte(startDate));
|
||||
} else if (endDate != null) {
|
||||
query.addCriteria(Criteria.where("publishedAt").lte(endDate));
|
||||
}
|
||||
|
||||
// Подсчет общего количества записей
|
||||
long totalCount = mongoTemplate.count(query, MarketItem.class);
|
||||
|
||||
// Применение пагинации и сортировки
|
||||
query.with(pageable);
|
||||
|
||||
// Выполнение запроса
|
||||
List<MarketItem> items = mongoTemplate.find(query, MarketItem.class);
|
||||
|
||||
// Создание объекта Page
|
||||
return new org.springframework.data.domain.PageImpl<>(items, pageable, totalCount);
|
||||
}
|
||||
|
||||
/**
|
||||
* Получение общего количества записей
|
||||
*/
|
||||
public long getTotalItemsCount() {
|
||||
return marketItemRepository.count();
|
||||
}
|
||||
|
||||
/**
|
||||
* Получение количества записей по источнику
|
||||
*/
|
||||
public long getItemsCountBySource(String sourceName) {
|
||||
return marketItemRepository.countBySourceName(sourceName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Получение всех уникальных источников
|
||||
*/
|
||||
public List<String> getAllSourceNames() {
|
||||
return marketItemRepository.findDistinctSourceNames();
|
||||
}
|
||||
|
||||
/**
|
||||
* Получение статистики по источникам
|
||||
*/
|
||||
public java.util.Map<String, Long> getSourceStatistics() {
|
||||
List<String> sources = getAllSourceNames();
|
||||
java.util.Map<String, Long> statistics = new java.util.HashMap<>();
|
||||
|
||||
for (String source : sources) {
|
||||
statistics.put(source, getItemsCountBySource(source));
|
||||
}
|
||||
|
||||
return statistics;
|
||||
}
|
||||
|
||||
/**
|
||||
* Парсинг даты из строки
|
||||
*/
|
||||
public LocalDateTime parseDate(String dateString) {
|
||||
if (dateString == null || dateString.trim().isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
// Поддерживаем несколько форматов даты
|
||||
String[] formats = {
|
||||
"yyyy-MM-dd",
|
||||
"yyyy-MM-dd'T'HH:mm:ss",
|
||||
"yyyy-MM-dd'T'HH:mm:ss.SSS",
|
||||
"yyyy-MM-dd HH:mm:ss"
|
||||
};
|
||||
|
||||
for (String format : formats) {
|
||||
try {
|
||||
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(format);
|
||||
return LocalDateTime.parse(dateString, formatter);
|
||||
} catch (Exception e) {
|
||||
// Пробуем следующий формат
|
||||
}
|
||||
}
|
||||
|
||||
logger.warn("Не удалось распарсить дату: {}", dateString);
|
||||
return null;
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Ошибка при парсинге даты: {}", dateString, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,16 @@
|
||||
spring.application.name=parser
|
||||
|
||||
# MongoDB Configuration
|
||||
# Option 1: Individual properties
|
||||
spring.data.mongodb.host=92.38.48.166
|
||||
spring.data.mongodb.port=27017
|
||||
spring.data.mongodb.database=parser_db
|
||||
spring.data.mongodb.username=konturai
|
||||
spring.data.mongodb.password=konturai2024
|
||||
spring.data.mongodb.authentication-database=admin
|
||||
|
||||
# Option 2: Connection URI (uncomment if individual properties don't work)
|
||||
# spring.data.mongodb.uri=mongodb://parser_user:parser_password@92.38.48.166:27017/parser_db?authSource=admin
|
||||
|
||||
# RSS Feed URLs
|
||||
rss.feed.url=https://kursiv.media/feed/
|
||||
@@ -13,6 +20,14 @@ rss.kapital.url=https://kapital.kz/rss/
|
||||
spring.task.scheduling.pool.size=2
|
||||
spring.task.scheduling.thread-name-prefix=scheduled-task-
|
||||
|
||||
# MongoDB Connection Settings
|
||||
spring.data.mongodb.connection-timeout=30000
|
||||
spring.data.mongodb.socket-timeout=30000
|
||||
spring.data.mongodb.server-selection-timeout=30000
|
||||
|
||||
# Logging Configuration
|
||||
logging.level.kz.konturai.parser.service.KursivParserService=INFO
|
||||
logging.level.kz.konturai.parser.service.KapitalParserService=INFO
|
||||
logging.level.org.springframework.scheduling=DEBUG
|
||||
logging.level.org.springframework.data.mongodb=INFO
|
||||
logging.level.com.mongodb=WARN
|
||||
|
||||
Reference in New Issue
Block a user