Skip to content

Commit

Permalink
“init”
Browse files Browse the repository at this point in the history
  • Loading branch information
self-programming-bio-robot committed Oct 2, 2018
0 parents commit c93193a
Show file tree
Hide file tree
Showing 14 changed files with 526 additions and 0 deletions.
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.gradle
/build/
!gradle/wrapper/gradle-wrapper.jar

/out/
65 changes: 65 additions & 0 deletions README.MD
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
**Тестовое задание Java**

Требуется реализовать REST-сервис по сбору статистики посещаемости WEB-сайта.

**Сборка**

Команда **gradle bootJar** соберет приложение Spring Boot в jar.

Собранный файл будет располагаться в build/libs

**Запуск**

Для запуска нужна СУБД Mongo. Для настройки подключения нужно отредактировать файл application.yml, указать хост, порт,
имя базы данных в соотвествующих полях.

Запустить можно командой **gradle bootRun**.

Или можно запустить собранный jar командой java -jar digitalzone-0.0.1-SNAPSHOT.jar. Отредактированный файл application.yml расположить рядом с jar.

**Тестирование**

Приложение имеет API c двумя endpoint:

**POST /event :**

Добавляет новое посещение страницы пользователем.

В теле сообщения нужно передать json создаваемоего объекта:

```$javascript
{
"userUUID": "d8898714-37ad-4327-a34b-faf7b3f87461", // ид пользователя
"pageUUID": "5218de4f-3f96-49d6-9ba7-d6f47c49d3e0" // ид страницы
}
```

Ответом будет статистика за день:

```$javascript
{
"amountAllVisits": 1, // количество визитов за день
"amountUniqueUser": 1 // количество уникальных пользователей за день
}
```

**GET /event :**

Возвращает отчет за период

В параметрах запроса нужно передать

`begin` - дата в формате dd.MM.yyyy (02.10.2018), начало периода включительно

`end` - дата в формате dd.MM.yyyy (02.10.2018), окончание периода не включительно

Ответом будет статистика за день:

```$javascript
{
"amountAllVisits": 1, // количество визитов
"amountUniqueUser": 1 // количество уникальных пользователей
"amountRegularUser": 1 // количество постоянных пользователей
}
```

34 changes: 34 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
buildscript {
ext {
springBootVersion = '2.0.4.RELEASE'
}
repositories {
mavenCentral()
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
}
}

apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'

group = 'space.zhdanov.testwork'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = 1.8

repositories {
mavenCentral()
}


dependencies {
implementation('org.springframework.boot:spring-boot-starter-data-mongodb-reactive')
implementation('org.springframework.boot:spring-boot-starter-webflux')
testImplementation('org.springframework.boot:spring-boot-starter-test')
testImplementation('io.projectreactor:reactor-test')
compileOnly 'org.projectlombok:lombok:1.16.8'
}

1 change: 1 addition & 0 deletions settings.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
rootProject.name = 'digitalzone'
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package space.zhdanov.testwork.digitalzone;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.mongodb.config.EnableMongoAuditing;
import org.springframework.data.mongodb.repository.config.EnableReactiveMongoRepositories;

@EnableMongoAuditing
@EnableReactiveMongoRepositories
@SpringBootApplication
public class DigitalzoneApplication {

public static void main(String[] args) {
SpringApplication.run(DigitalzoneApplication.class, args);
}
}
23 changes: 23 additions & 0 deletions src/main/java/space/zhdanov/testwork/digitalzone/Test.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package space.zhdanov.testwork.digitalzone;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import space.zhdanov.testwork.digitalzone.entity.Event;

import java.util.Date;
import java.util.UUID;

public class Test {

public static void main(String[] args) {
Event event = Event.builder().PageUUID(UUID.randomUUID()).UserUUID(UUID.randomUUID()).timestamp(new Date()).build();

ObjectMapper ob = new ObjectMapper();

try {
System.out.println(ob.writeValueAsString(event));
} catch (JsonProcessingException e) {
e.printStackTrace();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package space.zhdanov.testwork.digitalzone.controller;

import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Mono;
import space.zhdanov.testwork.digitalzone.entity.Event;
import space.zhdanov.testwork.digitalzone.entity.ResponseAggregationEvents;
import space.zhdanov.testwork.digitalzone.service.EventService;

import java.util.Date;
import java.util.UUID;

@RestController
@RequestMapping("/event")
public class EventController {

private final EventService eventService;

public EventController(EventService eventService) {
this.eventService = eventService;
}

@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public Mono<ResponseAggregationEvents> addEvent(@RequestBody Mono<Event> event) {
return eventService.addEvent(
event.map(it-> {
it.setTimestamp(new Date());
return it;
}))
.then(eventService.getAggregationDataForDay());
}

@GetMapping()
public Mono<ResponseAggregationEvents> getStatisticForPeriod(@RequestParam @DateTimeFormat(pattern="dd.MM.yyyy") Date begin,
@RequestParam @DateTimeFormat(pattern="dd.MM.yyyy") Date end) {
return eventService.getAggregationDataForPeriod(begin, end);
}
}
27 changes: 27 additions & 0 deletions src/main/java/space/zhdanov/testwork/digitalzone/entity/Event.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package space.zhdanov.testwork.digitalzone.entity;

import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.*;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;

import java.io.Serializable;
import java.util.Date;
import java.util.UUID;

@Data
@Builder
@Document
@NoArgsConstructor
@AllArgsConstructor
public class Event implements Serializable {

@Id
private String _id;

private UUID UserUUID;
private UUID PageUUID;
@JsonFormat(shape = JsonFormat.Shape.STRING, timezone = "GMT+3", pattern = "dd.MM.yyyy HH:mm:ss")
private Date timestamp;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package space.zhdanov.testwork.digitalzone.entity;

import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Builder;
import lombok.Data;

@JsonInclude(value = JsonInclude.Include.NON_NULL)
@Data
@Builder
public class ResponseAggregationEvents {

private Integer amountAllVisits;
private Integer amountUniqueUser;
private Integer amountRegularUser;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package space.zhdanov.testwork.digitalzone.repository;

import org.springframework.data.mongodb.repository.ReactiveMongoRepository;
import org.springframework.data.repository.reactive.ReactiveCrudRepository;
import space.zhdanov.testwork.digitalzone.entity.Event;

public interface EventRepository extends ReactiveMongoRepository<Event, String> {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package space.zhdanov.testwork.digitalzone.service;

import lombok.AllArgsConstructor;
import lombok.Data;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.ReactiveMongoTemplate;
import org.springframework.data.mongodb.core.aggregation.Aggregation;
import org.springframework.data.mongodb.core.aggregation.GroupOperation;
import org.springframework.data.mongodb.core.aggregation.MatchOperation;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import space.zhdanov.testwork.digitalzone.entity.Event;
import space.zhdanov.testwork.digitalzone.entity.ResponseAggregationEvents;
import space.zhdanov.testwork.digitalzone.repository.EventRepository;

import java.util.Calendar;
import java.util.Date;
import java.util.UUID;

import static org.springframework.data.mongodb.core.aggregation.Aggregation.group;
import static org.springframework.data.mongodb.core.aggregation.Aggregation.match;
import static org.springframework.data.mongodb.core.aggregation.Aggregation.newAggregation;

@Service
public class EventService {

private EventRepository eventRepository;
private ReactiveMongoTemplate mongoTemplate;

@Autowired
public EventService(EventRepository eventRepository, ReactiveMongoTemplate mongoTemplate) {
this.eventRepository = eventRepository;
this.mongoTemplate = mongoTemplate;
}

public Mono<Event> addEvent(Mono<Event> event) {
return mongoTemplate.insert(event);
}

public Mono<ResponseAggregationEvents> getAggregationDataForDay() {
Calendar calendar = Calendar.getInstance();
calendar.setTime(new Date());
calendar.set(Calendar.HOUR_OF_DAY, 0);
calendar.set(Calendar.MINUTE, 0);
calendar.set(Calendar.SECOND, 0);
calendar.set(Calendar.MILLISECOND, 0);

Date today = calendar.getTime();
calendar.add(Calendar.DAY_OF_MONTH, 1);
Date tomorrow = calendar.getTime();

GroupOperation group = group("UserUUID").count().as("countUsers");
MatchOperation match = match(Criteria.where("timestamp").gte(today).lt(tomorrow));
Aggregation aggregation = newAggregation(match, group);
Flux<GroupEvent> eventFlux = mongoTemplate.aggregate(aggregation, Event.class, GroupEvent.class);
return eventFlux.reduce(ResponseAggregationEvents.builder().amountAllVisits(0).amountUniqueUser(0).build(),
(a, b) -> {
a.setAmountAllVisits(a.getAmountAllVisits() + b.countUsers);
a.setAmountUniqueUser(a.getAmountUniqueUser() + 1);
return a;
});
}

public Mono<ResponseAggregationEvents> getAggregationDataForPeriod(Date begin, Date end) {
MatchOperation match = match(Criteria.where("timestamp").gte(begin).lt(end));
GroupOperation group = group("UserUUID", "PageUUID").count().as("amountVisits");
GroupOperation group2 = group("UserUUID").count().as("amountUniqueVisits")
.sum("amountVisits").as("amountAllVisits");
Aggregation aggregation = newAggregation(match, group, group2);
Flux<GroupVisit> eventFlux = mongoTemplate.aggregate(aggregation, Event.class, GroupVisit.class);

return eventFlux
.reduce(ResponseAggregationEvents.builder().amountAllVisits(0).amountUniqueUser(0).amountRegularUser(0).build(),
(a, b) -> {
a.setAmountAllVisits(a.getAmountAllVisits() + b.getAmountAllVisits());
a.setAmountUniqueUser(a.getAmountUniqueUser() + 1);
if (b.getAmountUniqueVisits() >= 10)
a.setAmountRegularUser(a.getAmountRegularUser() + 1);
return a;
});
}

@Data
@AllArgsConstructor
private class GroupEvent {

private UUID userUUID;
private Integer countUsers;
}

@Data
@AllArgsConstructor
private class GroupVisit {

private UUID userUUID;
private Integer amountUniqueVisits;
private Integer amountAllVisits;
}

}
9 changes: 9 additions & 0 deletions src/main/resources/application.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
server:
port: 8081

spring:
data:
mongodb:
host: localhost
port: 27017
database: digital
Loading

0 comments on commit c93193a

Please sign in to comment.