From d3c2d17933246440a3fe8f8b1c438f447b9615cd Mon Sep 17 00:00:00 2001 From: Alexandra Polunina Date: Sun, 13 Nov 2016 00:54:12 +0300 Subject: [PATCH 01/44] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8?= =?UTF-8?q?=D0=BB=D0=B0=20=D1=80=D0=B5=D0=B3=D0=B8=D1=81=D1=82=D1=80=D0=B0?= =?UTF-8?q?=D1=86=D0=B8=D1=8E=20=D0=BD=D0=BE=D0=B2=D0=BE=D0=B3=D0=BE=20?= =?UTF-8?q?=D1=83=D1=81=D1=82=D1=80=D0=BE=D0=B9=D1=81=D1=82=D0=B2=D0=B0=20?= =?UTF-8?q?=D0=BF=D1=80=D0=B8=20=D0=BF=D0=BE=D0=BB=D1=83=D1=87=D0=B5=D0=BD?= =?UTF-8?q?=D0=B8=D0=B8=20id=20=D1=81=D0=BB=D0=B5=D0=B4=D1=83=D1=8E=D1=89?= =?UTF-8?q?=D0=B5=D0=B3=D0=BE=20=D1=82=D1=80=D0=B5=D0=BA=D0=B0.=20=D0=98?= =?UTF-8?q?=D1=81=D0=BF=D1=80=D0=B0=D0=B2=D0=B8=D0=BB=D0=B0=20=D1=84=D0=BE?= =?UTF-8?q?=D1=80=D0=BC=D0=B0=D1=82=20=D0=B4=D0=B0=D1=82=D1=8B,=20=D1=87?= =?UTF-8?q?=D1=82=D0=BE=D0=B1=D1=8B=20=D0=BF=D0=B5=D1=80=D0=B5=D0=B4=D0=B0?= =?UTF-8?q?=D0=BD=D0=BD=D0=BE=D0=B5=20=D0=B2=D1=80=D0=B5=D0=BC=D1=8F=20?= =?UTF-8?q?=D1=81=D0=BE=D1=85=D1=80=D0=B0=D0=BD=D1=8F=D0=BB=D0=BE=D1=81?= =?UTF-8?q?=D1=8C.=20=D0=98=D0=B7=D0=BC=D0=B5=D0=BD=D0=B8=D0=BB=D0=B0=20?= =?UTF-8?q?=D0=BD=D0=B0=D0=B7=D0=B2=D0=B0=D0=BD=D0=B8=D1=8F=20=D0=BF=D0=BE?= =?UTF-8?q?=D0=BB=D0=B5=D0=B9=20=D0=B2=20=D1=81=D0=BE=D0=BE=D1=82=D0=B2?= =?UTF-8?q?=D0=B5=D1=82=D1=81=D1=82=D0=B2=D0=B8=D0=B8=20=D1=81=20=D0=B4?= =?UTF-8?q?=D0=BE=D0=BA=D1=83=D0=BC=D0=B5=D0=BD=D1=82=D0=B0=D1=86=D0=B8?= =?UTF-8?q?=D0=B5=D0=B9.=20=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8=D0=BB?= =?UTF-8?q?=D0=B0=20=D1=81=D0=BE=D1=85=D1=80=D0=B0=D0=BD=D0=B5=D0=BD=D0=B8?= =?UTF-8?q?=D0=B5=20=D1=80=D0=B5=D0=B9=D1=82=D0=B8=D0=BD=D0=B3=D0=B0.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/README.md | 8 +-- src/msg/masseage_en.properties | 2 +- src/msg/masseage_ru.properties | 2 +- .../main/java/ownradio/command/InitDb.java | 2 +- .../java/ownradio/domain/AbstractEntity.java | 16 ++--- src/src/main/java/ownradio/domain/Device.java | 8 ++- .../java/ownradio/domain/DownloadTrack.java | 6 +- .../main/java/ownradio/domain/History.java | 10 +-- src/src/main/java/ownradio/domain/Rating.java | 10 +-- src/src/main/java/ownradio/domain/Track.java | 4 +- src/src/main/java/ownradio/domain/User.java | 2 +- .../ownradio/repository/RatingRepository.java | 8 +++ .../service/impl/HistoryServiceImpl.java | 23 ++++++- .../service/impl/TrackServiceImpl.java | 10 +-- .../service/impl/UserServiceImpl.java | 1 + .../ownradio/web/rest/v2/TrackController.java | 8 +-- .../resources/application-prod.properties | 2 +- .../main/resources/data/postgresql/schema.sql | 63 ++++++++++++++----- .../repository/HistoryRepositoryTest.java | 22 +++---- .../repository/RatingRepositoryTest.java | 45 +++++++++++++ .../repository/TrackRepositoryTest.java | 2 +- .../ownradio/service/TrackServiceTest.java | 12 ++-- .../web/rest/v2/HistoryControllerTest.java | 8 +-- .../web/rest/v2/TrackControllerTest.java | 4 +- 24 files changed, 194 insertions(+), 84 deletions(-) create mode 100644 src/src/test/java/ownradio/repository/RatingRepositoryTest.java diff --git a/src/README.md b/src/README.md index b499500..d78d4f4 100644 --- a/src/README.md +++ b/src/README.md @@ -36,7 +36,7 @@ Web API ##### HttpStatus * `200, "OK"` – в теле ответа будет лежать трек -* `404, "Not Found"` – если трек с таким id не найден +* `404, "Not Found"` – если трек с таким recid не найден ### Получение следующего трека с сервера ##### GET /api/v2/tracks/{deviceId}/next @@ -44,14 +44,14 @@ Web API ##### HttpStatus * `200, "OK"` – в теле ответа будет лежать UUID трека -* `404, "Not Found"` – если трек с таким id не найден +* `404, "Not Found"` – если трек с таким recid не найден ### Сохранение истории треков ##### POST /api/v2/histories/{deviceId}/{trackId} * `{trackId}` – UUID прослушанного трека * `{deviceId}` – UUID устройства где был прослушан трек -* `lastListen` - Время последнего прослушивания или пропуска трека для данного пользователя -* `isListen` - Признак прослушан ли трек до конца: 1 - прослушан, -1 – нет +* `lastlisten` - Время последнего прослушивания или пропуска трека для данного пользователя +* `islisten` - Признак прослушан ли трек до конца: 1 - прослушан, -1 – нет * `method` - Метод выбора трека ##### HttpStatus diff --git a/src/msg/masseage_en.properties b/src/msg/masseage_en.properties index 2484f99..7fe122b 100644 --- a/src/msg/masseage_en.properties +++ b/src/msg/masseage_en.properties @@ -1 +1 @@ -id=\u041D\u0430\u0437\u0432\u0430\u043D\u0438\u0435 \u0438\u0434\u0435\u043D\u0442\u0438\u0444\u0438\u043A\u0430\u0442\u043E\u0440\u0430 \u043D\u0430 en +recid=\u041D\u0430\u0437\u0432\u0430\u043D\u0438\u0435 \u0438\u0434\u0435\u043D\u0442\u0438\u0444\u0438\u043A\u0430\u0442\u043E\u0440\u0430 \u043D\u0430 en diff --git a/src/msg/masseage_ru.properties b/src/msg/masseage_ru.properties index fd108d6..33d7fbd 100644 --- a/src/msg/masseage_ru.properties +++ b/src/msg/masseage_ru.properties @@ -1 +1 @@ -id=\u041D\u0430\u0437\u0432\u0430\u043D\u0438\u0435 \u0438\u0434\u0435\u043D\u0442\u0438\u0444\u0438\u043A\u0430\u0442\u043E\u0440\u0430 \u043D\u0430 ru +recid=\u041D\u0430\u0437\u0432\u0430\u043D\u0438\u0435 \u0438\u0434\u0435\u043D\u0442\u0438\u0444\u0438\u043A\u0430\u0442\u043E\u0440\u0430 \u043D\u0430 ru diff --git a/src/src/main/java/ownradio/command/InitDb.java b/src/src/main/java/ownradio/command/InitDb.java index c9fb3fc..4568789 100644 --- a/src/src/main/java/ownradio/command/InitDb.java +++ b/src/src/main/java/ownradio/command/InitDb.java @@ -23,6 +23,6 @@ public class InitDb implements CommandLineRunner { @Override public void run(String... strings) throws Exception { User user = userService.save(new User()); - log.debug("User id: {}", user.getId()); + log.debug("User recid: {}", user.getRecid()); } } diff --git a/src/src/main/java/ownradio/domain/AbstractEntity.java b/src/src/main/java/ownradio/domain/AbstractEntity.java index 84bef8f..d740f08 100644 --- a/src/src/main/java/ownradio/domain/AbstractEntity.java +++ b/src/src/main/java/ownradio/domain/AbstractEntity.java @@ -26,21 +26,21 @@ public abstract class AbstractEntity implements Serializable { @GeneratedValue(generator = "uuid") @GenericGenerator(name = "uuid", strategy = "uuid2") @Column(unique = true) - private UUID id; + private UUID recid; - private String name; + private String recname; - private Date createdAt; - private Date updatedAt; + private Date reccreated; + private Date recupdated; @PrePersist public void beforePersist() { - setCreatedAt(new Date()); + setReccreated(new Date()); } @PreUpdate public void beforeUpdate() { - setUpdatedAt(new Date()); + setRecupdated(new Date()); } @Override @@ -50,12 +50,12 @@ public boolean equals(Object o) { AbstractEntity that = (AbstractEntity) o; - return id != null ? id.equals(that.id) : that.id == null; + return recid != null ? recid.equals(that.recid) : that.recid == null; } @Override public int hashCode() { - return id != null ? id.hashCode() : 0; + return recid != null ? recid.hashCode() : 0; } } diff --git a/src/src/main/java/ownradio/domain/Device.java b/src/src/main/java/ownradio/domain/Device.java index 66d54cb..e6910df 100644 --- a/src/src/main/java/ownradio/domain/Device.java +++ b/src/src/main/java/ownradio/domain/Device.java @@ -23,8 +23,12 @@ @Table(name = "devices") public class Device extends AbstractEntity { @ManyToOne - @JoinColumn(name = "user_id") + @JoinColumn(name = "userid") private User user; - private String name; +// private String name; + public Device (User user, String name){ + setRecname(name); + setUser(user); + } } diff --git a/src/src/main/java/ownradio/domain/DownloadTrack.java b/src/src/main/java/ownradio/domain/DownloadTrack.java index eaece5f..9f710f3 100644 --- a/src/src/main/java/ownradio/domain/DownloadTrack.java +++ b/src/src/main/java/ownradio/domain/DownloadTrack.java @@ -20,14 +20,14 @@ @NoArgsConstructor @AllArgsConstructor @Entity -@Table(name = "download_tracks") +@Table(name = "downloadtracks") public class DownloadTrack extends AbstractEntity { @ManyToOne - @JoinColumn(name = "device_id") + @JoinColumn(name = "deviceid") private Device device; @ManyToOne - @JoinColumn(name = "track_id") + @JoinColumn(name = "trackid") private Track track; } diff --git a/src/src/main/java/ownradio/domain/History.java b/src/src/main/java/ownradio/domain/History.java index bacd4f0..78c33de 100644 --- a/src/src/main/java/ownradio/domain/History.java +++ b/src/src/main/java/ownradio/domain/History.java @@ -24,20 +24,20 @@ public class History extends AbstractEntity { @ManyToOne - @JoinColumn(name = "track_id") + @JoinColumn(name = "trackid") private Track track; - @DateTimeFormat(pattern = "dd/MM/yyyy") + @DateTimeFormat(pattern = "dd/MM/yyyy H:m:s") @Column(nullable = false) - private Date lastListen; + private Date lastlisten; @Column(nullable = false) - private int isListen; // 1, -1 + private int islisten; // 1, -1 @Column(nullable = false) private String method; @ManyToOne - @JoinColumn(name = "device_id") + @JoinColumn(name = "deviceid") private Device device; } diff --git a/src/src/main/java/ownradio/domain/Rating.java b/src/src/main/java/ownradio/domain/Rating.java index fe40ecf..92a19f2 100644 --- a/src/src/main/java/ownradio/domain/Rating.java +++ b/src/src/main/java/ownradio/domain/Rating.java @@ -23,17 +23,17 @@ @Table(name = "ratings") public class Rating extends AbstractEntity { @ManyToOne - @JoinColumn(name = "user_id") + @JoinColumn(name = "userid") private User user; @ManyToOne - @JoinColumn(name = "track_id") + @JoinColumn(name = "trackid") private Track track; - @DateTimeFormat(pattern = "dd/MM/yyyy") + @DateTimeFormat(pattern = "dd/MM/yyyy H:m:s") @Column(nullable = false) - private Date lastListen; + private Date lastlisten; @Column(nullable = false) - private Integer ratingSum; + private Integer ratingsum; } diff --git a/src/src/main/java/ownradio/domain/Track.java b/src/src/main/java/ownradio/domain/Track.java index 2ea7e90..45f4829 100644 --- a/src/src/main/java/ownradio/domain/Track.java +++ b/src/src/main/java/ownradio/domain/Track.java @@ -23,10 +23,10 @@ public class Track extends AbstractEntity { private String path; @ManyToOne - @JoinColumn(name = "device_id") + @JoinColumn(name = "deviceid") private Device device; @Column(nullable = false) - private String localDevicePathUpload; + private String localdevicepathupload; } diff --git a/src/src/main/java/ownradio/domain/User.java b/src/src/main/java/ownradio/domain/User.java index 58bdc8b..1f1c8c9 100644 --- a/src/src/main/java/ownradio/domain/User.java +++ b/src/src/main/java/ownradio/domain/User.java @@ -19,6 +19,6 @@ @Table(name = "users") public class User extends AbstractEntity { public User(String name) { - setName(name); + setRecname(name); } } diff --git a/src/src/main/java/ownradio/repository/RatingRepository.java b/src/src/main/java/ownradio/repository/RatingRepository.java index b947905..b739402 100644 --- a/src/src/main/java/ownradio/repository/RatingRepository.java +++ b/src/src/main/java/ownradio/repository/RatingRepository.java @@ -1,8 +1,12 @@ package ownradio.repository; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; import ownradio.domain.Rating; +import ownradio.domain.Track; +import ownradio.domain.User; +import java.util.Date; import java.util.UUID; /** @@ -11,4 +15,8 @@ * @author Alpenov Tanat */ public interface RatingRepository extends JpaRepository { + + Rating findByUser(User user); + + Rating findByUserAndTrack(User user, Track track); } diff --git a/src/src/main/java/ownradio/service/impl/HistoryServiceImpl.java b/src/src/main/java/ownradio/service/impl/HistoryServiceImpl.java index d1e7785..554298a 100644 --- a/src/src/main/java/ownradio/service/impl/HistoryServiceImpl.java +++ b/src/src/main/java/ownradio/service/impl/HistoryServiceImpl.java @@ -3,22 +3,43 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import ownradio.domain.History; +import ownradio.domain.Rating; +import ownradio.domain.User; import ownradio.repository.HistoryRepository; +import ownradio.repository.RatingRepository; import ownradio.service.HistoryService; @Service public class HistoryServiceImpl implements HistoryService { private final HistoryRepository historyRepository; + private final RatingRepository ratingRepository; @Autowired - public HistoryServiceImpl(HistoryRepository historyRepository) { + public HistoryServiceImpl(HistoryRepository historyRepository, RatingRepository ratingRepository) { this.historyRepository = historyRepository; + this.ratingRepository = ratingRepository; } @Override public void save(History history) { historyRepository.saveAndFlush(history); + + Rating rating = ratingRepository.findByUserAndTrack(history.getDevice().getUser(), history.getTrack()); + if(rating != null) { + int ratingsum = rating.getRatingsum() + history.getIslisten(); + rating.setLastlisten(history.getLastlisten()); + rating.setRatingsum(ratingsum); + ratingRepository.saveAndFlush(rating); + } + else { + rating = new Rating(); + rating.setUser(history.getDevice().getUser()); + rating.setTrack(history.getTrack()); + rating.setLastlisten(history.getLastlisten()); + rating.setRatingsum(history.getIslisten()); + ratingRepository.saveAndFlush(rating); + } } } diff --git a/src/src/main/java/ownradio/service/impl/TrackServiceImpl.java b/src/src/main/java/ownradio/service/impl/TrackServiceImpl.java index 76ae61f..5e4936d 100644 --- a/src/src/main/java/ownradio/service/impl/TrackServiceImpl.java +++ b/src/src/main/java/ownradio/service/impl/TrackServiceImpl.java @@ -29,7 +29,7 @@ public Track getById(UUID id) { } @Override - @Transactional(readOnly = true) + @Transactional public UUID getNextTrackId(UUID deviceId) { return trackRepository.getNextTrackId(deviceId); } @@ -37,15 +37,15 @@ public UUID getNextTrackId(UUID deviceId) { @Override @Transactional public void save(Track track, MultipartFile file) { - boolean result = trackRepository.registerTrack(track.getId(), track.getLocalDevicePathUpload(), track.getPath(), track.getDevice().getId()); + boolean result = trackRepository.registerTrack(track.getRecid(), track.getLocaldevicepathupload(), track.getPath(), track.getDevice().getRecid()); if (!result) { throw new RuntimeException(); } - Track storeTrack = trackRepository.findOne(track.getId()); + Track storeTrack = trackRepository.findOne(track.getRecid()); - String dirName = storeTrack.getDevice().getUser().getId().toString(); - String fileName = storeTrack.getId() + "." + StringUtils.getFilenameExtension(file.getOriginalFilename()); + String dirName = storeTrack.getDevice().getUser().getRecid().toString(); + String fileName = storeTrack.getRecid() + "." + StringUtils.getFilenameExtension(file.getOriginalFilename()); String filePath = ResourceUtil.save(dirName, fileName, file); storeTrack.setPath(filePath); diff --git a/src/src/main/java/ownradio/service/impl/UserServiceImpl.java b/src/src/main/java/ownradio/service/impl/UserServiceImpl.java index 09816ef..c5c88dc 100644 --- a/src/src/main/java/ownradio/service/impl/UserServiceImpl.java +++ b/src/src/main/java/ownradio/service/impl/UserServiceImpl.java @@ -2,6 +2,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import ownradio.domain.User; import ownradio.repository.UserRepository; import ownradio.service.UserService; diff --git a/src/src/main/java/ownradio/web/rest/v2/TrackController.java b/src/src/main/java/ownradio/web/rest/v2/TrackController.java index 33082ce..beb0639 100644 --- a/src/src/main/java/ownradio/web/rest/v2/TrackController.java +++ b/src/src/main/java/ownradio/web/rest/v2/TrackController.java @@ -45,14 +45,14 @@ private static class TrackDTO { public Track getTrack() { Device device = new Device(); - device.setId(deviceId); + device.setRecid(deviceId); Track track = new Track(); - track.setId(fileGuid); - track.setName(fileName); + track.setRecid(fileGuid); + track.setRecname(fileName); track.setDevice(device); track.setPath("---"); - track.setLocalDevicePathUpload(filePath); + track.setLocaldevicepathupload(filePath); return track; } diff --git a/src/src/main/resources/application-prod.properties b/src/src/main/resources/application-prod.properties index b1a92f6..dde430b 100644 --- a/src/src/main/resources/application-prod.properties +++ b/src/src/main/resources/application-prod.properties @@ -12,7 +12,7 @@ spring.http.multipart.max-request-size=256Mb spring.jackson.property-naming-strategy=CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES spring.jpa.properties.hibernate.format_sql=true spring.jpa.show-sql=true -spring.jpa.hibernate.ddl-auto=create +spring.jpa.hibernate.ddl-auto=update logging.file=logs/app.log logging.level.ownradio=info logging.level.org.springframework=warn diff --git a/src/src/main/resources/data/postgresql/schema.sql b/src/src/main/resources/data/postgresql/schema.sql index 328a09d..9b065fb 100644 --- a/src/src/main/resources/data/postgresql/schema.sql +++ b/src/src/main/resources/data/postgresql/schema.sql @@ -1,10 +1,43 @@ CREATE OR REPLACE FUNCTION getnexttrackid(IN i_deviceid UUID) RETURNS SETOF UUID AS ' -BEGIN +DECLARE + i_userid uuid = i_deviceid; + BEGIN + -- Добавляем устройство, если его еще не существует + -- Если ID устройства еще нет в БД + IF NOT EXISTS(SELECT recid + FROM devices + WHERE recid = i_deviceid) + THEN + + -- Добавляем нового пользователя + INSERT INTO users (recid, recname, reccreated) SELECT + i_userid, + ''New user recname'', + now(); + + -- Добавляем новое устройство + INSERT INTO devices (recid, userid, recname, reccreated) SELECT + i_deviceid, + i_userid, + ''New device recname'', + now(); + ELSE + SELECT (SELECT userid + FROM devices + WHERE recid = i_deviceid + LIMIT 1) + INTO i_userid; + END IF; + RETURN QUERY - SELECT id + SELECT tracks.recid FROM tracks + LEFT JOIN + ratings + ON tracks.recid = ratings.trackid AND ratings.userid = i_userid + WHERE ratings.ratingsum >=0 OR ratings.ratingsum is null ORDER BY RANDOM() LIMIT 1; END; @@ -30,14 +63,12 @@ CREATE OR REPLACE FUNCTION registertrack( RETURNS BOOLEAN AS ' DECLARE - i_userid UUID; + i_userid UUID = i_deviceid; i_historyid UUID; i_ratingid UUID; BEGIN CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; SELECT uuid_generate_v1() - INTO i_userid; - SELECT uuid_generate_v1() INTO i_historyid; SELECT uuid_generate_v1() INTO i_ratingid; @@ -50,41 +81,41 @@ BEGIN -- Добавляем устройство, если его еще не существует -- Если ID устройства еще нет в БД - IF NOT EXISTS(SELECT id + IF NOT EXISTS(SELECT recid FROM devices - WHERE id = i_deviceid) + WHERE recid = i_deviceid) THEN -- Добавляем нового пользователя - INSERT INTO users (id, name, created_at) SELECT + INSERT INTO users (recid, recname, reccreated) SELECT i_userid, - ''New user name'', + ''New user recname'', now(); -- Добавляем новое устройство - INSERT INTO devices (id, user_id, name, created_at) SELECT + INSERT INTO devices (recid, userid, recname, reccreated) SELECT i_deviceid, i_userid, - ''New device name'', + ''New device recname'', now(); ELSE - SELECT (SELECT user_id + SELECT (SELECT userid FROM devices - WHERE id = i_deviceid + WHERE recid = i_deviceid LIMIT 1) INTO i_userid; END IF; -- Добавляем трек в базу данных - INSERT INTO tracks (id, local_device_path_upload, path, device_id, created_at) + INSERT INTO tracks (recid, localdevicepathupload, path, deviceid, reccreated) VALUES (i_trackid, i_localdevicepathupload, i_path, i_deviceid, now()); -- Добавляем запись о прослушивании трека в таблицу истории прослушивания - INSERT INTO histories (id, device_id, track_id, is_listen, last_listen, method, created_at) + INSERT INTO histories (recid, deviceid, trackid, islisten, lastlisten, method, reccreated) VALUES (i_historyid, i_deviceid, i_trackid, 1, now(), ''method'', now()); -- Добавляем запись в таблицу рейтингов - INSERT INTO ratings (id, user_id, track_id, last_listen, rating_sum, created_at) + INSERT INTO ratings (recid, userid, trackid, lastlisten, ratingsum, reccreated) VALUES (i_ratingid, i_userid, i_trackid, now(), 1, now()); RETURN TRUE; diff --git a/src/src/test/java/ownradio/repository/HistoryRepositoryTest.java b/src/src/test/java/ownradio/repository/HistoryRepositoryTest.java index 76228bd..5ccf898 100644 --- a/src/src/test/java/ownradio/repository/HistoryRepositoryTest.java +++ b/src/src/test/java/ownradio/repository/HistoryRepositoryTest.java @@ -42,25 +42,25 @@ public void setUp() throws Exception { @Test public void createdAt() throws Exception { - assertThat(history.getCreatedAt(), not(nullValue())); - assertThat(history.getCreatedAt().toString(), is(new Date().toString())); + assertThat(history.getReccreated(), not(nullValue())); + assertThat(history.getReccreated().toString(), is(new Date().toString())); } @Test public void updatedAt() throws Exception { - assertThat(history.getCreatedAt(), not(nullValue())); - assertThat(history.getCreatedAt().toString(), is(new Date().toString())); - assertThat(history.getUpdatedAt(), is(nullValue())); + assertThat(history.getReccreated(), not(nullValue())); + assertThat(history.getReccreated().toString(), is(new Date().toString())); + assertThat(history.getRecupdated(), is(nullValue())); - History storeHistory = historyRepository.findOne(history.getId()); - storeHistory.setIsListen(1); + History storeHistory = historyRepository.findOne(history.getRecid()); + storeHistory.setIslisten(1); historyRepository.saveAndFlush(storeHistory); - assertThat(storeHistory.getCreatedAt(), not(nullValue())); - assertThat(storeHistory.getCreatedAt().toString(), is(history.getCreatedAt().toString())); + assertThat(storeHistory.getReccreated(), not(nullValue())); + assertThat(storeHistory.getReccreated().toString(), is(history.getReccreated().toString())); - assertThat(storeHistory.getUpdatedAt(), not(nullValue())); - assertThat(storeHistory.getUpdatedAt().toString(), is(new Date().toString())); + assertThat(storeHistory.getRecupdated(), not(nullValue())); + assertThat(storeHistory.getRecupdated().toString(), is(new Date().toString())); } diff --git a/src/src/test/java/ownradio/repository/RatingRepositoryTest.java b/src/src/test/java/ownradio/repository/RatingRepositoryTest.java new file mode 100644 index 0000000..3930678 --- /dev/null +++ b/src/src/test/java/ownradio/repository/RatingRepositoryTest.java @@ -0,0 +1,45 @@ +package ownradio.repository; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.junit4.SpringRunner; +import ownradio.domain.Rating; +import ownradio.domain.Track; +import ownradio.domain.User; + +import java.util.UUID; + +import static org.junit.Assert.*; + +/** + * Created by a.polunina on 10.11.2016. + */ +@ActiveProfiles("prod") +@RunWith(SpringRunner.class) +@SpringBootTest +//@DataJpaTest +public class RatingRepositoryTest { + @Autowired + private RatingRepository ratingRepository; + @Test + public void findByUser() throws Exception { + User user = new User(); + user.setRecid(UUID.fromString("bfa8137b-c917-4496-8fe7-39202322d257")); + Rating rating = ratingRepository.findByUser(user); + System.out.println(rating); + } + + @Test + public void findByUserAndTrack() throws Exception { + User user = new User(); + Track track = new Track(); + user.setRecid(UUID.fromString("bfa8137b-c917-4496-8fe7-39202322d257")); + track.setRecid(UUID.fromString("bfa8137b-c917-4496-8fe7-39202322d257")); + Rating rating = ratingRepository.findByUserAndTrack(user, track); + System.out.println(rating); + } +} \ No newline at end of file diff --git a/src/src/test/java/ownradio/repository/TrackRepositoryTest.java b/src/src/test/java/ownradio/repository/TrackRepositoryTest.java index 42f2951..6c7618a 100644 --- a/src/src/test/java/ownradio/repository/TrackRepositoryTest.java +++ b/src/src/test/java/ownradio/repository/TrackRepositoryTest.java @@ -47,7 +47,7 @@ public void getNextTrackId() throws Exception { Set trackSet = new HashSet<>(); for (int i = 0; i < 3; i++) { - UUID track = trackRepository.getNextTrackId(device.getId()); + UUID track = trackRepository.getNextTrackId(device.getRecid()); trackSet.add(track); } diff --git a/src/src/test/java/ownradio/service/TrackServiceTest.java b/src/src/test/java/ownradio/service/TrackServiceTest.java index c3369cb..abe1f81 100644 --- a/src/src/test/java/ownradio/service/TrackServiceTest.java +++ b/src/src/test/java/ownradio/service/TrackServiceTest.java @@ -39,13 +39,13 @@ public class TrackServiceTest { public void setUp() throws Exception { trackService = new TrackServiceImpl(trackRepository); expected = new Track(); - expected.setId(trackId); + expected.setRecid(trackId); User user = new User(); - user.setId(userId); + user.setRecid(userId); Device device = new Device(user, "123"); - device.setId(deviceId); + device.setRecid(deviceId); expected.setDevice(device); } @@ -60,15 +60,15 @@ public void getNextTrackId() throws Exception { UUID actual = trackService.getNextTrackId(trackId); - assertThat(actual, equalTo(expected.getId())); + assertThat(actual, equalTo(expected.getRecid())); } @Test public void save() throws Exception { MockMultipartFile correctFile = new MockMultipartFile("file", "test.mp3", "text/plain", "Text".getBytes()); - given(this.trackRepository.registerTrack(expected.getId(), expected.getLocalDevicePathUpload(), expected.getPath(), expected.getDevice().getId())).willReturn(true); - given(this.trackRepository.findOne(expected.getId())).willReturn(expected); + given(this.trackRepository.registerTrack(expected.getRecid(), expected.getLocaldevicepathupload(), expected.getPath(), expected.getDevice().getRecid())).willReturn(true); + given(this.trackRepository.findOne(expected.getRecid())).willReturn(expected); trackService.save(expected, correctFile); assertThat(new File(expected.getPath()).exists(), is(true)); diff --git a/src/src/test/java/ownradio/web/rest/v2/HistoryControllerTest.java b/src/src/test/java/ownradio/web/rest/v2/HistoryControllerTest.java index f3f80bb..b2d98f0 100644 --- a/src/src/test/java/ownradio/web/rest/v2/HistoryControllerTest.java +++ b/src/src/test/java/ownradio/web/rest/v2/HistoryControllerTest.java @@ -69,8 +69,8 @@ public void saveStatusIsOk() throws Exception { given(this.deviceService.getById(DEVICE_UUID)).willReturn(device); mockMvc.perform(post("/api/v2/histories/{deviceId}/{trackId}", DEVICE_UUID, TRACK_UUID) - .param("lastListen", "12/12/2016") - .param("isListen", "1") + .param("lastlisten", "12/12/2016") + .param("islisten", "1") .param("method", "method") ) .andDo(print()) @@ -88,8 +88,8 @@ public void saveStatusIsInternalServerError() throws Exception { doThrow(RuntimeException.class).when(this.historyService).save(any(History.class)); mockMvc.perform(post("/api/v2/histories/{deviceId}/{trackId}", DEVICE_UUID, TRACK_UUID) - .param("lastListen", "12/12/2016") - .param("isListen", "1") + .param("lastlisten", "12/12/2016") + .param("islisten", "1") .param("method", "method") ) .andDo(print()) diff --git a/src/src/test/java/ownradio/web/rest/v2/TrackControllerTest.java b/src/src/test/java/ownradio/web/rest/v2/TrackControllerTest.java index 270e5d4..138a18f 100644 --- a/src/src/test/java/ownradio/web/rest/v2/TrackControllerTest.java +++ b/src/src/test/java/ownradio/web/rest/v2/TrackControllerTest.java @@ -62,8 +62,8 @@ public class TrackControllerTest { @Before public void setUp() throws Exception { - user.setId(USER_UUID); - device.setId(DEVICE_UUID); + user.setRecid(USER_UUID); + device.setRecid(DEVICE_UUID); device.setUser(user); track = new Track(PATH, device, "---"); From 5d5ce6767594027b6f3393f560d7dfa25690f3cb Mon Sep 17 00:00:00 2001 From: Alexandra Polunina Date: Sun, 13 Nov 2016 12:58:08 +0300 Subject: [PATCH 02/44] =?UTF-8?q?=D0=97=D0=B0=D0=BC=D0=B5=D0=BD=D0=B8?= =?UTF-8?q?=D0=BB=D0=B0=20=D1=81=D0=BE=D0=B7=D0=B4=D0=B0=D0=BD=D0=B8=D0=B5?= =?UTF-8?q?=20=D0=91=D0=94=20=D0=BD=D0=B0=20=D0=BE=D0=B1=D0=BD=D0=BE=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D0=B8=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/src/main/resources/application-dev.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/src/main/resources/application-dev.properties b/src/src/main/resources/application-dev.properties index ab8ef07..87765f0 100644 --- a/src/src/main/resources/application-dev.properties +++ b/src/src/main/resources/application-dev.properties @@ -9,7 +9,7 @@ spring.http.multipart.max-request-size=256Mb spring.jackson.property-naming-strategy=CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES spring.jpa.properties.hibernate.format_sql=true spring.jpa.show-sql=true -spring.jpa.hibernate.ddl-auto=create +spring.jpa.hibernate.ddl-auto=update logging.file=logs/app.log logging.level.ownradio=debug logging.level.org.springframework=warn From 1aaa0518afefe59daf27fb06c81b3b81c6a482b6 Mon Sep 17 00:00:00 2001 From: Alexandra Polunina Date: Sun, 13 Nov 2016 17:20:30 +0300 Subject: [PATCH 03/44] =?UTF-8?q?=D0=B8=D1=81=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=BA=D0=BB=D1=8E=D1=87=D0=B0?= =?UTF-8?q?=20=D0=BB=D0=BE=D0=BA=D0=B0=D0=BB=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/msg/masseage_en.properties | 2 +- src/msg/masseage_ru.properties | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/msg/masseage_en.properties b/src/msg/masseage_en.properties index 7fe122b..2484f99 100644 --- a/src/msg/masseage_en.properties +++ b/src/msg/masseage_en.properties @@ -1 +1 @@ -recid=\u041D\u0430\u0437\u0432\u0430\u043D\u0438\u0435 \u0438\u0434\u0435\u043D\u0442\u0438\u0444\u0438\u043A\u0430\u0442\u043E\u0440\u0430 \u043D\u0430 en +id=\u041D\u0430\u0437\u0432\u0430\u043D\u0438\u0435 \u0438\u0434\u0435\u043D\u0442\u0438\u0444\u0438\u043A\u0430\u0442\u043E\u0440\u0430 \u043D\u0430 en diff --git a/src/msg/masseage_ru.properties b/src/msg/masseage_ru.properties index 33d7fbd..fd108d6 100644 --- a/src/msg/masseage_ru.properties +++ b/src/msg/masseage_ru.properties @@ -1 +1 @@ -recid=\u041D\u0430\u0437\u0432\u0430\u043D\u0438\u0435 \u0438\u0434\u0435\u043D\u0442\u0438\u0444\u0438\u043A\u0430\u0442\u043E\u0440\u0430 \u043D\u0430 ru +id=\u041D\u0430\u0437\u0432\u0430\u043D\u0438\u0435 \u0438\u0434\u0435\u043D\u0442\u0438\u0444\u0438\u043A\u0430\u0442\u043E\u0440\u0430 \u043D\u0430 ru From efad82626f97c5bf4e74a0943fd06af267f5618a Mon Sep 17 00:00:00 2001 From: Alexandra Polunina Date: Sun, 13 Nov 2016 18:53:42 +0300 Subject: [PATCH 04/44] =?UTF-8?q?=D0=B8=D1=81=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=B8=D0=BB=D0=B0=20=D1=84=D0=BE=D1=80=D0=BC=D0=B0=D1=82=20?= =?UTF-8?q?=D0=B4=D0=B0=D1=82=D1=8B=20=D0=B2=20=D1=82=D0=B5=D1=81=D1=82?= =?UTF-8?q?=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../test/java/ownradio/web/rest/v2/HistoryControllerTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/src/test/java/ownradio/web/rest/v2/HistoryControllerTest.java b/src/src/test/java/ownradio/web/rest/v2/HistoryControllerTest.java index b2d98f0..bfb95fe 100644 --- a/src/src/test/java/ownradio/web/rest/v2/HistoryControllerTest.java +++ b/src/src/test/java/ownradio/web/rest/v2/HistoryControllerTest.java @@ -69,7 +69,7 @@ public void saveStatusIsOk() throws Exception { given(this.deviceService.getById(DEVICE_UUID)).willReturn(device); mockMvc.perform(post("/api/v2/histories/{deviceId}/{trackId}", DEVICE_UUID, TRACK_UUID) - .param("lastlisten", "12/12/2016") + .param("lastlisten", "12/12/2016 11:11:11") .param("islisten", "1") .param("method", "method") ) @@ -88,7 +88,7 @@ public void saveStatusIsInternalServerError() throws Exception { doThrow(RuntimeException.class).when(this.historyService).save(any(History.class)); mockMvc.perform(post("/api/v2/histories/{deviceId}/{trackId}", DEVICE_UUID, TRACK_UUID) - .param("lastlisten", "12/12/2016") + .param("lastlisten", "12/12/2016 11:11:11") .param("islisten", "1") .param("method", "method") ) From 44ebc189b969de51d24e3144cace0ea72c98c559 Mon Sep 17 00:00:00 2001 From: Alexandra Polunina Date: Sun, 13 Nov 2016 22:09:43 +0300 Subject: [PATCH 05/44] =?UTF-8?q?=D0=9E=D1=82=D0=BC=D0=B5=D0=BD=D0=B8?= =?UTF-8?q?=D0=BB=D0=B0=20=D0=B8=D0=B7=D0=BC=D0=B5=D0=BD=D0=B5=D0=BD=D0=B8?= =?UTF-8?q?=D0=B5=20api?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/README.md | 4 ++-- src/src/main/java/ownradio/domain/DownloadTrack.java | 5 +---- src/src/main/java/ownradio/domain/History.java | 8 ++++---- .../java/ownradio/service/impl/HistoryServiceImpl.java | 9 ++++----- src/src/main/resources/data/postgresql/schema.sql | 4 ++-- .../java/ownradio/repository/HistoryRepositoryTest.java | 2 +- .../java/ownradio/web/rest/v2/HistoryControllerTest.java | 8 ++++---- 7 files changed, 18 insertions(+), 22 deletions(-) diff --git a/src/README.md b/src/README.md index d78d4f4..4f3a4cd 100644 --- a/src/README.md +++ b/src/README.md @@ -50,8 +50,8 @@ Web API ##### POST /api/v2/histories/{deviceId}/{trackId} * `{trackId}` – UUID прослушанного трека * `{deviceId}` – UUID устройства где был прослушан трек -* `lastlisten` - Время последнего прослушивания или пропуска трека для данного пользователя -* `islisten` - Признак прослушан ли трек до конца: 1 - прослушан, -1 – нет +* `lastListen` - Время последнего прослушивания или пропуска трека для данного пользователя +* `isListen` - Признак прослушан ли трек до конца: 1 - прослушан, -1 – нет * `method` - Метод выбора трека ##### HttpStatus diff --git a/src/src/main/java/ownradio/domain/DownloadTrack.java b/src/src/main/java/ownradio/domain/DownloadTrack.java index 9f710f3..447832d 100644 --- a/src/src/main/java/ownradio/domain/DownloadTrack.java +++ b/src/src/main/java/ownradio/domain/DownloadTrack.java @@ -5,10 +5,7 @@ import lombok.NoArgsConstructor; import lombok.Setter; -import javax.persistence.Entity; -import javax.persistence.JoinColumn; -import javax.persistence.ManyToOne; -import javax.persistence.Table; +import javax.persistence.*; /** * Сущность для хранения информации о скаченных треках diff --git a/src/src/main/java/ownradio/domain/History.java b/src/src/main/java/ownradio/domain/History.java index 78c33de..c259b02 100644 --- a/src/src/main/java/ownradio/domain/History.java +++ b/src/src/main/java/ownradio/domain/History.java @@ -28,11 +28,11 @@ public class History extends AbstractEntity { private Track track; @DateTimeFormat(pattern = "dd/MM/yyyy H:m:s") - @Column(nullable = false) - private Date lastlisten; + @Column(name = "lastlisten", nullable = false) + private Date lastListen; - @Column(nullable = false) - private int islisten; // 1, -1 + @Column(name = "islisten", nullable = false) + private int isListen; // 1, -1 @Column(nullable = false) private String method; diff --git a/src/src/main/java/ownradio/service/impl/HistoryServiceImpl.java b/src/src/main/java/ownradio/service/impl/HistoryServiceImpl.java index 554298a..e3edd07 100644 --- a/src/src/main/java/ownradio/service/impl/HistoryServiceImpl.java +++ b/src/src/main/java/ownradio/service/impl/HistoryServiceImpl.java @@ -4,7 +4,6 @@ import org.springframework.stereotype.Service; import ownradio.domain.History; import ownradio.domain.Rating; -import ownradio.domain.User; import ownradio.repository.HistoryRepository; import ownradio.repository.RatingRepository; import ownradio.service.HistoryService; @@ -28,8 +27,8 @@ public void save(History history) { Rating rating = ratingRepository.findByUserAndTrack(history.getDevice().getUser(), history.getTrack()); if(rating != null) { - int ratingsum = rating.getRatingsum() + history.getIslisten(); - rating.setLastlisten(history.getLastlisten()); + int ratingsum = rating.getRatingsum() + history.getIsListen(); + rating.setLastlisten(history.getLastListen()); rating.setRatingsum(ratingsum); ratingRepository.saveAndFlush(rating); } @@ -37,8 +36,8 @@ public void save(History history) { rating = new Rating(); rating.setUser(history.getDevice().getUser()); rating.setTrack(history.getTrack()); - rating.setLastlisten(history.getLastlisten()); - rating.setRatingsum(history.getIslisten()); + rating.setLastlisten(history.getLastListen()); + rating.setRatingsum(history.getIsListen()); ratingRepository.saveAndFlush(rating); } } diff --git a/src/src/main/resources/data/postgresql/schema.sql b/src/src/main/resources/data/postgresql/schema.sql index 9b065fb..a9916e3 100644 --- a/src/src/main/resources/data/postgresql/schema.sql +++ b/src/src/main/resources/data/postgresql/schema.sql @@ -111,11 +111,11 @@ BEGIN VALUES (i_trackid, i_localdevicepathupload, i_path, i_deviceid, now()); -- Добавляем запись о прослушивании трека в таблицу истории прослушивания - INSERT INTO histories (recid, deviceid, trackid, islisten, lastlisten, method, reccreated) + INSERT INTO histories (recid, deviceid, trackid, isListen, lastListen, method, reccreated) VALUES (i_historyid, i_deviceid, i_trackid, 1, now(), ''method'', now()); -- Добавляем запись в таблицу рейтингов - INSERT INTO ratings (recid, userid, trackid, lastlisten, ratingsum, reccreated) + INSERT INTO ratings (recid, userid, trackid, lastListen, ratingsum, reccreated) VALUES (i_ratingid, i_userid, i_trackid, now(), 1, now()); RETURN TRUE; diff --git a/src/src/test/java/ownradio/repository/HistoryRepositoryTest.java b/src/src/test/java/ownradio/repository/HistoryRepositoryTest.java index 5ccf898..6585d8d 100644 --- a/src/src/test/java/ownradio/repository/HistoryRepositoryTest.java +++ b/src/src/test/java/ownradio/repository/HistoryRepositoryTest.java @@ -53,7 +53,7 @@ public void updatedAt() throws Exception { assertThat(history.getRecupdated(), is(nullValue())); History storeHistory = historyRepository.findOne(history.getRecid()); - storeHistory.setIslisten(1); + storeHistory.setIsListen(1); historyRepository.saveAndFlush(storeHistory); assertThat(storeHistory.getReccreated(), not(nullValue())); diff --git a/src/src/test/java/ownradio/web/rest/v2/HistoryControllerTest.java b/src/src/test/java/ownradio/web/rest/v2/HistoryControllerTest.java index bfb95fe..b8b425c 100644 --- a/src/src/test/java/ownradio/web/rest/v2/HistoryControllerTest.java +++ b/src/src/test/java/ownradio/web/rest/v2/HistoryControllerTest.java @@ -69,8 +69,8 @@ public void saveStatusIsOk() throws Exception { given(this.deviceService.getById(DEVICE_UUID)).willReturn(device); mockMvc.perform(post("/api/v2/histories/{deviceId}/{trackId}", DEVICE_UUID, TRACK_UUID) - .param("lastlisten", "12/12/2016 11:11:11") - .param("islisten", "1") + .param("lastListen", "12/12/2016 11:11:11") + .param("isListen", "1") .param("method", "method") ) .andDo(print()) @@ -88,8 +88,8 @@ public void saveStatusIsInternalServerError() throws Exception { doThrow(RuntimeException.class).when(this.historyService).save(any(History.class)); mockMvc.perform(post("/api/v2/histories/{deviceId}/{trackId}", DEVICE_UUID, TRACK_UUID) - .param("lastlisten", "12/12/2016 11:11:11") - .param("islisten", "1") + .param("lastListen", "12/12/2016 11:11:11") + .param("isListen", "1") .param("method", "method") ) .andDo(print()) From 8dad49a6653df153ef4a7399eecdb0ce793dc26b Mon Sep 17 00:00:00 2001 From: Alexandra Polunina Date: Mon, 14 Nov 2016 13:03:00 +0300 Subject: [PATCH 06/44] =?UTF-8?q?=D0=A3=D0=B1=D1=80=D0=B0=D0=BB=D0=B0=20?= =?UTF-8?q?=D0=B0=D1=82=D1=82=D0=B5=D1=80=D0=BD=20=D1=84=D0=BE=D1=80=D0=BC?= =?UTF-8?q?=D0=B0=D1=82=D0=B0=20=D0=B4=D0=B0=D1=82=D1=8B,=20=D1=87=D1=82?= =?UTF-8?q?=D0=BE=D0=B1=D1=8B=20=D0=BF=D1=80=D0=B8=D0=BD=D0=B8=D0=BC=D0=B0?= =?UTF-8?q?=D0=BB=D0=B0=D1=81=D1=8C=20=D0=B4=D0=B0=D1=82=D0=B0=20=D0=B8=20?= =?UTF-8?q?=D1=81=D0=BE=20=D0=B2=D1=80=D0=B5=D0=BC=D0=B5=D0=BD=D0=B5=D0=BC?= =?UTF-8?q?=20=D0=B8=20=D0=B1=D0=B5=D0=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/src/main/java/ownradio/domain/History.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/src/main/java/ownradio/domain/History.java b/src/src/main/java/ownradio/domain/History.java index c259b02..2e6d881 100644 --- a/src/src/main/java/ownradio/domain/History.java +++ b/src/src/main/java/ownradio/domain/History.java @@ -27,7 +27,7 @@ public class History extends AbstractEntity { @JoinColumn(name = "trackid") private Track track; - @DateTimeFormat(pattern = "dd/MM/yyyy H:m:s") +// @DateTimeFormat(pattern = "dd/MM/yyyy H:m:s") @Column(name = "lastlisten", nullable = false) private Date lastListen; From 3d68861d3974204bde2c1cf5dbeca3817951e6ea Mon Sep 17 00:00:00 2001 From: Alexandra Polunina Date: Mon, 14 Nov 2016 17:24:50 +0300 Subject: [PATCH 07/44] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8?= =?UTF-8?q?=D0=BB=D0=B0=20=D1=82=D1=80=D0=B0=D0=BD=D0=B7=D0=B0=D0=BA=D1=86?= =?UTF-8?q?=D0=B8=D1=8E=20=D0=BF=D1=80=D0=B8=20=D1=81=D0=BE=D1=85=D1=80?= =?UTF-8?q?=D0=B0=D0=BD=D0=B5=D0=BD=D0=B8=D0=B8=20=D0=B8=D1=81=D1=82=D0=BE?= =?UTF-8?q?=D1=80=D0=B8=D0=B8=20=D0=B8=20=D1=80=D0=B5=D0=B9=D1=82=D0=B8?= =?UTF-8?q?=D0=BD=D0=B3=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/ownradio/service/impl/HistoryServiceImpl.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/src/main/java/ownradio/service/impl/HistoryServiceImpl.java b/src/src/main/java/ownradio/service/impl/HistoryServiceImpl.java index e3edd07..cf40353 100644 --- a/src/src/main/java/ownradio/service/impl/HistoryServiceImpl.java +++ b/src/src/main/java/ownradio/service/impl/HistoryServiceImpl.java @@ -2,6 +2,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import ownradio.domain.History; import ownradio.domain.Rating; import ownradio.repository.HistoryRepository; @@ -20,7 +21,7 @@ public HistoryServiceImpl(HistoryRepository historyRepository, RatingRepository this.ratingRepository = ratingRepository; } - + @Transactional @Override public void save(History history) { historyRepository.saveAndFlush(history); From 1111903aee6e34d7b4ae03efa84cb8fb35b6c93d Mon Sep 17 00:00:00 2001 From: Alexandra Polunina Date: Tue, 15 Nov 2016 10:09:13 +0300 Subject: [PATCH 08/44] =?UTF-8?q?=D0=92=D0=B5=D1=80=D0=BD=D1=83=D0=BB?= =?UTF-8?q?=D0=B0=20=D1=84=D0=BE=D1=80=D0=BC=D0=B0=D1=82=20=D0=B4=D0=B0?= =?UTF-8?q?=D1=82=D1=8B=20"dd/MM/yyyy"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/src/main/java/ownradio/domain/History.java | 2 +- src/src/main/java/ownradio/domain/Rating.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/src/main/java/ownradio/domain/History.java b/src/src/main/java/ownradio/domain/History.java index 2e6d881..c41c696 100644 --- a/src/src/main/java/ownradio/domain/History.java +++ b/src/src/main/java/ownradio/domain/History.java @@ -27,7 +27,7 @@ public class History extends AbstractEntity { @JoinColumn(name = "trackid") private Track track; -// @DateTimeFormat(pattern = "dd/MM/yyyy H:m:s") + @DateTimeFormat(pattern = "dd/MM/yyyy") @Column(name = "lastlisten", nullable = false) private Date lastListen; diff --git a/src/src/main/java/ownradio/domain/Rating.java b/src/src/main/java/ownradio/domain/Rating.java index 92a19f2..78ab1b9 100644 --- a/src/src/main/java/ownradio/domain/Rating.java +++ b/src/src/main/java/ownradio/domain/Rating.java @@ -30,7 +30,7 @@ public class Rating extends AbstractEntity { @JoinColumn(name = "trackid") private Track track; - @DateTimeFormat(pattern = "dd/MM/yyyy H:m:s") + @DateTimeFormat(pattern = "dd/MM/yyyy") @Column(nullable = false) private Date lastlisten; From 5fe017c4601c6b91ce23bed639021184b6c69d99 Mon Sep 17 00:00:00 2001 From: Alexandra Polunina Date: Tue, 29 Nov 2016 19:09:56 +0300 Subject: [PATCH 09/44] =?UTF-8?q?=D0=A0=D0=B0=D0=B1=D0=BE=D1=87=D0=B0?= =?UTF-8?q?=D1=8F=20=D0=B2=D0=B5=D1=80=D1=81=D0=B8=D1=8F,=20=D0=B8=D1=81?= =?UTF-8?q?=D1=82=D0=BE=D1=80=D0=B8=D1=8F=20=D1=81=D0=BE=D1=85=D1=80=D0=B0?= =?UTF-8?q?=D0=BD=D1=8F=D0=B5=D1=82=D1=81=D1=8F,=20getnexttrack=20=D0=B2?= =?UTF-8?q?=D1=8B=D0=B4=D0=B0=D0=B5=D1=82=20=D0=B8=D0=BD=D1=84=D1=83=20?= =?UTF-8?q?=D0=BE=20=D1=82=D1=80=D0=B5=D0=BA=D0=B5=20(=D0=BF=D0=BE=D0=BA?= =?UTF-8?q?=D0=B0=20=D0=B7=D0=B0=D0=B3=D0=BB=D1=83=D1=88=D0=BA=D0=B0)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pom.xml | 2 +- .../main/java/ownradio/command/InitDb.java | 3 + .../java/ownradio/domain/AbstractEntity.java | 12 +- .../main/java/ownradio/domain/History.java | 10 +- .../main/java/ownradio/domain/NextTrack.java | 19 ++ src/src/main/java/ownradio/domain/Rating.java | 6 +- src/src/main/java/ownradio/domain/Track.java | 12 ++ .../ownradio/repository/TrackRepository.java | 8 + .../java/ownradio/service/TrackService.java | 5 + .../service/impl/TrackServiceImpl.java | 13 ++ .../web/rest/v2/HistoryController.java | 15 +- .../ownradio/web/rest/v2/TrackController.java | 5 +- .../web/rest/v3/HistoryControllerV3.java | 61 ++++++ .../web/rest/v3/TrackControllerV3.java | 114 +++++++++++ .../main/resources/application-dev.properties | 2 +- .../resources/application-prod.properties | 11 +- .../main/resources/data/postgresql/schema.sql | 109 ++++++++++ .../repository/HistoryRepositoryTest.java | 13 +- .../repository/TrackRepositoryTest.java | 6 +- .../web/rest/v2/HistoryControllerTest.java | 30 ++- .../web/rest/v2/TrackControllerTest.java | 19 +- .../web/rest/v3/HistoryControllerV3Test.java | 122 +++++++++++ .../web/rest/v3/TrackControllerV3Test.java | 191 ++++++++++++++++++ 23 files changed, 746 insertions(+), 42 deletions(-) create mode 100644 src/src/main/java/ownradio/domain/NextTrack.java create mode 100644 src/src/main/java/ownradio/web/rest/v3/HistoryControllerV3.java create mode 100644 src/src/main/java/ownradio/web/rest/v3/TrackControllerV3.java create mode 100644 src/src/test/java/ownradio/web/rest/v3/HistoryControllerV3Test.java create mode 100644 src/src/test/java/ownradio/web/rest/v3/TrackControllerV3Test.java diff --git a/src/pom.xml b/src/pom.xml index 7e14138..539354c 100644 --- a/src/pom.xml +++ b/src/pom.xml @@ -14,7 +14,7 @@ org.springframework.boot spring-boot-starter-parent - 1.4.0.RELEASE + 1.4.2.RELEASE diff --git a/src/src/main/java/ownradio/command/InitDb.java b/src/src/main/java/ownradio/command/InitDb.java index 4568789..ab61a8b 100644 --- a/src/src/main/java/ownradio/command/InitDb.java +++ b/src/src/main/java/ownradio/command/InitDb.java @@ -8,6 +8,8 @@ import ownradio.domain.User; import ownradio.service.UserService; +import java.util.TimeZone; + /** * Класс инициализирует БД первоначальными данными * @@ -22,6 +24,7 @@ public class InitDb implements CommandLineRunner { @Override public void run(String... strings) throws Exception { + TimeZone.setDefault(TimeZone.getTimeZone("Etc/UTC")); User user = userService.save(new User()); log.debug("User recid: {}", user.getRecid()); } diff --git a/src/src/main/java/ownradio/domain/AbstractEntity.java b/src/src/main/java/ownradio/domain/AbstractEntity.java index d740f08..991ae20 100644 --- a/src/src/main/java/ownradio/domain/AbstractEntity.java +++ b/src/src/main/java/ownradio/domain/AbstractEntity.java @@ -7,6 +7,7 @@ import javax.persistence.*; import java.io.Serializable; +import java.util.Calendar; import java.util.Date; import java.util.UUID; @@ -30,17 +31,20 @@ public abstract class AbstractEntity implements Serializable { private String recname; - private Date reccreated; - private Date recupdated; + @Temporal(TemporalType.TIMESTAMP) + private Calendar reccreated; + + @Temporal(TemporalType.TIMESTAMP) + private Calendar recupdated; @PrePersist public void beforePersist() { - setReccreated(new Date()); + setReccreated(Calendar.getInstance()); } @PreUpdate public void beforeUpdate() { - setRecupdated(new Date()); + setRecupdated(Calendar.getInstance()); } @Override diff --git a/src/src/main/java/ownradio/domain/History.java b/src/src/main/java/ownradio/domain/History.java index c41c696..679afa3 100644 --- a/src/src/main/java/ownradio/domain/History.java +++ b/src/src/main/java/ownradio/domain/History.java @@ -8,6 +8,7 @@ import org.springframework.format.annotation.DateTimeFormat; import javax.persistence.*; +import java.util.Calendar; import java.util.Date; /** @@ -27,16 +28,19 @@ public class History extends AbstractEntity { @JoinColumn(name = "trackid") private Track track; - @DateTimeFormat(pattern = "dd/MM/yyyy") + @DateTimeFormat(pattern = "dd-MM-yyyy'T'H:m:s") + @Temporal(TemporalType.TIMESTAMP) @Column(name = "lastlisten", nullable = false) - private Date lastListen; + private Calendar lastListen; @Column(name = "islisten", nullable = false) private int isListen; // 1, -1 - @Column(nullable = false) +// @Column(nullable = false) private String method; + private Integer methodid; + @ManyToOne @JoinColumn(name = "deviceid") private Device device; diff --git a/src/src/main/java/ownradio/domain/NextTrack.java b/src/src/main/java/ownradio/domain/NextTrack.java new file mode 100644 index 0000000..1dc01fb --- /dev/null +++ b/src/src/main/java/ownradio/domain/NextTrack.java @@ -0,0 +1,19 @@ +package ownradio.domain; + +import lombok.Getter; +import lombok.Setter; +import org.hibernate.annotations.Type; + +import java.util.UUID; + +/** + * Created by a.polunina on 29.11.2016. + */ +@Getter +@Setter +public class NextTrack { + private Integer methodid; + + @Type(type="pg-uuid") + private UUID trackid; +} diff --git a/src/src/main/java/ownradio/domain/Rating.java b/src/src/main/java/ownradio/domain/Rating.java index 78ab1b9..02e753f 100644 --- a/src/src/main/java/ownradio/domain/Rating.java +++ b/src/src/main/java/ownradio/domain/Rating.java @@ -8,6 +8,7 @@ import org.springframework.format.annotation.DateTimeFormat; import javax.persistence.*; +import java.util.Calendar; import java.util.Date; /** @@ -30,9 +31,10 @@ public class Rating extends AbstractEntity { @JoinColumn(name = "trackid") private Track track; - @DateTimeFormat(pattern = "dd/MM/yyyy") + @DateTimeFormat(pattern = "dd-MM-yyyy'T'H:m:s") @Column(nullable = false) - private Date lastlisten; + @Temporal(TemporalType.TIMESTAMP) + private Calendar lastlisten; @Column(nullable = false) private Integer ratingsum; diff --git a/src/src/main/java/ownradio/domain/Track.java b/src/src/main/java/ownradio/domain/Track.java index 45f4829..e8ad14a 100644 --- a/src/src/main/java/ownradio/domain/Track.java +++ b/src/src/main/java/ownradio/domain/Track.java @@ -29,4 +29,16 @@ public class Track extends AbstractEntity { @Column(nullable = false) private String localdevicepathupload; + private Integer length; + + private String artist; + + private Integer size; + + private Integer iscorrect; + + private Integer isfilledinfo; + + private Integer iscensorial; + } diff --git a/src/src/main/java/ownradio/repository/TrackRepository.java b/src/src/main/java/ownradio/repository/TrackRepository.java index 33af031..56d772f 100644 --- a/src/src/main/java/ownradio/repository/TrackRepository.java +++ b/src/src/main/java/ownradio/repository/TrackRepository.java @@ -2,8 +2,12 @@ import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; +import ownradio.domain.NextTrack; import ownradio.domain.Track; +import java.sql.ResultSet; +import java.util.Collection; +import java.util.List; import java.util.UUID; /** @@ -15,6 +19,10 @@ public interface TrackRepository extends JpaRepository { @Query(value = "select getnexttrackid_string(?1)", nativeQuery = true) UUID getNextTrackId(UUID deviceId); + @Query(value = "select getnexttrackid_string(?1)", nativeQuery = true) +// @Query(value = "select getnexttrack_string(?1)", nativeQuery = true) + UUID getNextTrackV2(UUID deviceId); + @Query(value = "select registertrack(?1, ?2, ?3, ?4)", nativeQuery = true) boolean registerTrack(UUID trackId, String localDevicePathUpload, String path, UUID deviceId); } diff --git a/src/src/main/java/ownradio/service/TrackService.java b/src/src/main/java/ownradio/service/TrackService.java index f7aac70..c94d0b0 100644 --- a/src/src/main/java/ownradio/service/TrackService.java +++ b/src/src/main/java/ownradio/service/TrackService.java @@ -1,8 +1,11 @@ package ownradio.service; import org.springframework.web.multipart.MultipartFile; +import ownradio.domain.NextTrack; import ownradio.domain.Track; +import java.util.Collection; +import java.util.List; import java.util.UUID; /** @@ -16,5 +19,7 @@ public interface TrackService { UUID getNextTrackId(UUID deviceId); + NextTrack getNextTrackIdV2(UUID deviceId); + void save(Track track, MultipartFile file); } diff --git a/src/src/main/java/ownradio/service/impl/TrackServiceImpl.java b/src/src/main/java/ownradio/service/impl/TrackServiceImpl.java index 5e4936d..29b77c4 100644 --- a/src/src/main/java/ownradio/service/impl/TrackServiceImpl.java +++ b/src/src/main/java/ownradio/service/impl/TrackServiceImpl.java @@ -5,11 +5,15 @@ import org.springframework.transaction.annotation.Transactional; import org.springframework.util.StringUtils; import org.springframework.web.multipart.MultipartFile; +import ownradio.domain.NextTrack; import ownradio.domain.Track; import ownradio.repository.TrackRepository; import ownradio.service.TrackService; import ownradio.util.ResourceUtil; +import java.sql.ResultSet; +import java.util.Collection; +import java.util.List; import java.util.UUID; @Service @@ -34,6 +38,15 @@ public UUID getNextTrackId(UUID deviceId) { return trackRepository.getNextTrackId(deviceId); } + @Override + @Transactional + public NextTrack getNextTrackIdV2(UUID deviceId) { + NextTrack nextTrack = new NextTrack(); + nextTrack.setTrackid(trackRepository.getNextTrackV2(deviceId)); + nextTrack.setMethodid(1);//!!! + return nextTrack; + } + @Override @Transactional public void save(Track track, MultipartFile file) { diff --git a/src/src/main/java/ownradio/web/rest/v2/HistoryController.java b/src/src/main/java/ownradio/web/rest/v2/HistoryController.java index e972f1a..cc71f08 100644 --- a/src/src/main/java/ownradio/web/rest/v2/HistoryController.java +++ b/src/src/main/java/ownradio/web/rest/v2/HistoryController.java @@ -1,10 +1,14 @@ package ownradio.web.rest.v2; +import lombok.Data; +import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.format.annotation.DateTimeFormat; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.WebDataBinder; import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; import ownradio.domain.Device; import ownradio.domain.History; import ownradio.domain.Track; @@ -14,7 +18,9 @@ import ownradio.service.TrackService; import ownradio.service.UserService; +import javax.persistence.Column; import java.beans.PropertyEditorSupport; +import java.util.Date; import java.util.UUID; /** @@ -22,6 +28,7 @@ * * @author Alpenov Tanat */ +@Slf4j @RestController @RequestMapping("/api/v2/histories") public class HistoryController { @@ -37,8 +44,10 @@ public HistoryController(HistoryService historyService, TrackService trackServic } @RequestMapping(value = "/{deviceId}/{trackId}", method = RequestMethod.POST) - public ResponseEntity save(@PathVariable UUID deviceId, @PathVariable UUID trackId, History history) { + public ResponseEntity save(@PathVariable UUID deviceId, @PathVariable UUID trackId, @RequestBody History history) { try { + log.info("{} {}",deviceId.toString(),trackId.toString()); + log.info("{} {} {}",history.getLastListen(), history.getIsListen(), history.getMethod()); Track track = trackService.getById(trackId); Device device = deviceService.getById(deviceId); @@ -51,9 +60,9 @@ public ResponseEntity save(@PathVariable UUID deviceId, @PathVariable UUID track historyService.save(history); - return new ResponseEntity(HttpStatus.OK); + return new ResponseEntity(history, HttpStatus.OK); } catch (Exception e) { - return new ResponseEntity(HttpStatus.INTERNAL_SERVER_ERROR); + return new ResponseEntity(history, HttpStatus.INTERNAL_SERVER_ERROR); } } diff --git a/src/src/main/java/ownradio/web/rest/v2/TrackController.java b/src/src/main/java/ownradio/web/rest/v2/TrackController.java index beb0639..c37c9c6 100644 --- a/src/src/main/java/ownradio/web/rest/v2/TrackController.java +++ b/src/src/main/java/ownradio/web/rest/v2/TrackController.java @@ -6,10 +6,7 @@ import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; import ownradio.domain.Device; import ownradio.domain.Track; diff --git a/src/src/main/java/ownradio/web/rest/v3/HistoryControllerV3.java b/src/src/main/java/ownradio/web/rest/v3/HistoryControllerV3.java new file mode 100644 index 0000000..99d66ed --- /dev/null +++ b/src/src/main/java/ownradio/web/rest/v3/HistoryControllerV3.java @@ -0,0 +1,61 @@ +package ownradio.web.rest.v3; + +import lombok.Data; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.format.annotation.DateTimeFormat; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import ownradio.domain.Device; +import ownradio.domain.History; +import ownradio.domain.Track; +import ownradio.service.DeviceService; +import ownradio.service.HistoryService; +import ownradio.service.TrackService; + +import java.util.Date; +import java.util.UUID; + +/** + * Created by a.polunina on 28.11.2016. + */ +@Slf4j +@RestController +@RequestMapping("/v3/histories") +public class HistoryControllerV3 { + private final HistoryService historyService; + private final TrackService trackService; + private final DeviceService deviceService; + + @Autowired + public HistoryControllerV3(HistoryService historyService, TrackService trackService, DeviceService deviceService) { + this.historyService = historyService; + this.trackService = trackService; + this.deviceService = deviceService; + } + + @RequestMapping(value = "/{deviceId}/{trackId}", method = RequestMethod.POST) + public ResponseEntity save(@PathVariable UUID deviceId, @PathVariable UUID trackId, @RequestBody History history) { + try { + log.info("{} {}",deviceId.toString(),trackId.toString()); + log.info("{} {} {}",history.getLastListen(), history.getIsListen(), history.getMethod()); + Track track = trackService.getById(trackId); + Device device = deviceService.getById(deviceId); + + if (track == null || device == null) { + throw new RuntimeException("Track or Device is null"); + } + + history.setTrack(track); + history.setDevice(device); + + historyService.save(history); + + return new ResponseEntity(HttpStatus.OK); + } catch (Exception e) { + return new ResponseEntity(HttpStatus.INTERNAL_SERVER_ERROR); + } + } + +} diff --git a/src/src/main/java/ownradio/web/rest/v3/TrackControllerV3.java b/src/src/main/java/ownradio/web/rest/v3/TrackControllerV3.java new file mode 100644 index 0000000..ca9ff0b --- /dev/null +++ b/src/src/main/java/ownradio/web/rest/v3/TrackControllerV3.java @@ -0,0 +1,114 @@ +package ownradio.web.rest.v3; + +import lombok.Data; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; +import ownradio.domain.Device; +import ownradio.domain.NextTrack; +import ownradio.domain.Track; +import ownradio.repository.TrackRepository; +import ownradio.service.TrackService; +import ownradio.util.ResourceUtil; + +import java.util.*; + +/** + * Created by a.polunina on 28.11.2016. + */ +@Slf4j +@RestController +@RequestMapping(value = "/v3/tracks") +public class TrackControllerV3 { + + private final TrackService trackService; + private final TrackRepository trackRepository; + + @Autowired + public TrackControllerV3(TrackService trackService, TrackRepository trackRepository) { + this.trackService = trackService; + this.trackRepository = trackRepository; + } + + @Data + private static class TrackDTO { + private UUID fileGuid; + private String fileName; + private String filePath; + private UUID deviceId; + private MultipartFile musicFile; + + public Track getTrack() { + Device device = new Device(); + device.setRecid(deviceId); + + Track track = new Track(); + track.setRecid(fileGuid); + track.setRecname(fileName); + track.setDevice(device); + track.setPath("---"); + track.setLocaldevicepathupload(filePath); + + return track; + } + } + + @RequestMapping(method = RequestMethod.POST) + public ResponseEntity save(ownradio.web.rest.v3.TrackControllerV3.TrackDTO trackDTO) { + if (trackDTO.getMusicFile().isEmpty()) { + return new ResponseEntity(HttpStatus.BAD_REQUEST); + } + + try { + trackService.save(trackDTO.getTrack(), trackDTO.getMusicFile()); + + return new ResponseEntity(HttpStatus.CREATED); + } catch (Exception e) { + return new ResponseEntity(HttpStatus.INTERNAL_SERVER_ERROR); + } + + } + + @RequestMapping(value = "/{id}", method = RequestMethod.GET) + public ResponseEntity getTrack(@PathVariable UUID id) { + Track track = trackService.getById(id); + + if (track != null) { + byte[] bytes = ResourceUtil.read(track.getPath()); + return new ResponseEntity<>(bytes, getHttpAudioHeaders(), HttpStatus.OK); + } else { + return new ResponseEntity<>(HttpStatus.NOT_FOUND); + } + } + + private HttpHeaders getHttpAudioHeaders() { + HttpHeaders responseHeaders = new HttpHeaders(); + responseHeaders.add("Content-Type", "audio/mpeg"); + return responseHeaders; + } + + @RequestMapping(value = "/{deviceId}/next", method = RequestMethod.GET) + public ResponseEntity getNextTrack(@PathVariable UUID deviceId) { + NextTrack nextTrack = trackService.getNextTrackIdV2(deviceId); + UUID trackId = nextTrack.getTrackid(); + + if (trackId != null) { + Track track = trackRepository.findOne(trackId); + log.info("{} {} {}", track.getRecname(), track.getArtist(), track.getLength()); + Map trackInfo = new HashMap<>(); + trackInfo.put("id", trackId.toString()); + trackInfo.put("length", String.valueOf(track.getLength())); + trackInfo.put("name", track.getRecname()); + trackInfo.put("artist", track.getArtist()); + trackInfo.put("methodid", nextTrack.getMethodid().toString()); + return new ResponseEntity<>(trackInfo, HttpStatus.OK); + } else { + return new ResponseEntity<>(HttpStatus.NOT_FOUND); + } + } + +} \ No newline at end of file diff --git a/src/src/main/resources/application-dev.properties b/src/src/main/resources/application-dev.properties index 87765f0..5814481 100644 --- a/src/src/main/resources/application-dev.properties +++ b/src/src/main/resources/application-dev.properties @@ -6,7 +6,7 @@ spring.datasource.password= spring.datasource.platform=org.hibernate.dialect.H2Dialect spring.http.multipart.max-file-size=256Mb spring.http.multipart.max-request-size=256Mb -spring.jackson.property-naming-strategy=CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES +#spring.jackson.property-naming-strategy=CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES spring.jpa.properties.hibernate.format_sql=true spring.jpa.show-sql=true spring.jpa.hibernate.ddl-auto=update diff --git a/src/src/main/resources/application-prod.properties b/src/src/main/resources/application-prod.properties index dde430b..ef6b7df 100644 --- a/src/src/main/resources/application-prod.properties +++ b/src/src/main/resources/application-prod.properties @@ -1,6 +1,9 @@ -spring.datasource.url=jdbc:postgresql://127.0.0.1:5432/ownradio -spring.datasource.username=tanat -spring.datasource.password=123456 +#spring.datasource.url=jdbc:postgresql://127.0.0.1:5432/ownradio +#spring.datasource.username=tanat +#spring.datasource.password=123456 +spring.datasource.url=jdbc:postgresql://10.10.0.67:5432/ownRadioJavaTest +spring.datasource.username=a.polunina +spring.datasource.password=6ImtqMGAt1 spring.datasource.platform=org.hibernate.dialect.PostgreSQL94Dialect spring.datasource.test-while-idle=true spring.datasource.test-on-borrow=true @@ -9,7 +12,7 @@ spring.datasource.time-between-eviction-runs-millis=5000 spring.datasource.min-evictable-idle-time-millis=60000 spring.http.multipart.max-file-size=256Mb spring.http.multipart.max-request-size=256Mb -spring.jackson.property-naming-strategy=CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES +#spring.jackson.property-naming-strategy=CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES spring.jpa.properties.hibernate.format_sql=true spring.jpa.show-sql=true spring.jpa.hibernate.ddl-auto=update diff --git a/src/src/main/resources/data/postgresql/schema.sql b/src/src/main/resources/data/postgresql/schema.sql index a9916e3..918be2c 100644 --- a/src/src/main/resources/data/postgresql/schema.sql +++ b/src/src/main/resources/data/postgresql/schema.sql @@ -122,3 +122,112 @@ BEGIN END; ' LANGUAGE plpgsql; + + +CREATE OR REPLACE FUNCTION getnexttrackid_v2(i_deviceid uuid, OUT o_methodid integer, OUT o_trackid uuid) + AS +' +DECLARE + i_userid uuid = i_deviceid; + rnd integer = (select trunc(random() * 10)); +BEGIN + -- Добавляем устройство, если его еще не существует + -- Если ID устройства еще нет в БД + IF NOT EXISTS(SELECT recid + FROM devices + WHERE recid = i_deviceid) + THEN + + -- Добавляем нового пользователя + INSERT INTO users (recid, recname, reccreated) SELECT + i_userid, + ''New user recname'', + now(); + + -- Добавляем новое устройство + INSERT INTO devices (recid, userid, recname, reccreated) SELECT + i_deviceid, + i_userid, + ''New device recname'', + now(); + ELSE + SELECT (SELECT userid + FROM devices + WHERE recid = i_deviceid + LIMIT 1) + INTO i_userid; + END IF; + + -- Выбираем следующий трек + + -- В 9/10 случаях выбираем трек из треков пользователя (добавленных им или прослушанных до конца) + -- с положительным рейтингом, за исключением прослушанных за последние сутки + IF (rnd > 1) + THEN + o_methodid = 2; + o_trackid=(SELECT trackid + FROM ratings + WHERE userid = i_userid + AND lastlisten < localtimestamp - interval ''1 day'' + AND ratingsum >= 0 + ORDER BY RANDOM() + LIMIT 1); + + -- Если такой трек найден - выход из функции, возврат найденного значения + IF FOUND + THEN RETURN; + END IF; + END IF; + + -- В 1/10 случае выбираем случайный трек из ни разу не прослушанных пользователем треков + o_methodid = 3; + o_trackid=(SELECT recid + FROM tracks + WHERE recid NOT IN + (SELECT trackid + FROM ratings + WHERE userid = i_userid) + ORDER BY RANDOM() + LIMIT 1); + + -- Если такой трек найден - выход из функции, возврат найденного значения + IF FOUND + THEN RETURN; + END IF; + + -- Если предыдущие запросы вернули null, выбираем случайный трек + o_methodid = 1; + o_trackid=(SELECT recid + FROM tracks + ORDER BY RANDOM() + LIMIT 1); + RETURN; +END; +' +LANGUAGE plpgsql; + +-- CREATE TYPE myintuuid AS ( +-- methodid INTEGER, +-- trackid CHARACTER VARYING +-- ); + + +-- CREATE TYPE myintstring AS ( +-- methodid INTEGER, +-- trackid CHARACTER VARYING +-- ); +-- +CREATE OR REPLACE FUNCTION public.getnexttrack_string(i_deviceid uuid) + RETURNS CHARACTER VARYING AS +' +DECLARE + tmpres myintuuid; + res myintstring; +BEGIN + tmpres = getnexttrackid_v2(i_deviceid); + res.methodid = tmpres.methodid; + res.trackid = CAST ((tmpres.trackid) AS CHARACTER VARYING); + RETURN res.trackid; +END; +' +LANGUAGE plpgsql; \ No newline at end of file diff --git a/src/src/test/java/ownradio/repository/HistoryRepositoryTest.java b/src/src/test/java/ownradio/repository/HistoryRepositoryTest.java index 6585d8d..aa7e1c6 100644 --- a/src/src/test/java/ownradio/repository/HistoryRepositoryTest.java +++ b/src/src/test/java/ownradio/repository/HistoryRepositoryTest.java @@ -12,6 +12,7 @@ import ownradio.domain.Track; import ownradio.domain.User; +import java.util.Calendar; import java.util.Date; import static org.hamcrest.CoreMatchers.nullValue; @@ -34,22 +35,22 @@ public class HistoryRepositoryTest { public void setUp() throws Exception { User user = entityManager.persist(new User()); Device device = entityManager.persist(new Device(user, "1")); - Track track = entityManager.persist(new Track("1", device, "1")); + Track track = entityManager.persist(new Track("1", device, "1", 0, "", 0, null, null, null)); - history = new History(track, new Date(), 0, "post", device); + history = new History(track, Calendar.getInstance(), 0, "post", 1, device); entityManager.persist(history); } @Test public void createdAt() throws Exception { assertThat(history.getReccreated(), not(nullValue())); - assertThat(history.getReccreated().toString(), is(new Date().toString())); + assertThat(history.getReccreated().getTime().toString(), is(Calendar.getInstance().getTime().toString())); } @Test public void updatedAt() throws Exception { assertThat(history.getReccreated(), not(nullValue())); - assertThat(history.getReccreated().toString(), is(new Date().toString())); + assertThat(history.getReccreated().getTime().toString(), is(Calendar.getInstance().getTime().toString())); assertThat(history.getRecupdated(), is(nullValue())); History storeHistory = historyRepository.findOne(history.getRecid()); @@ -57,10 +58,10 @@ public void updatedAt() throws Exception { historyRepository.saveAndFlush(storeHistory); assertThat(storeHistory.getReccreated(), not(nullValue())); - assertThat(storeHistory.getReccreated().toString(), is(history.getReccreated().toString())); + assertThat(storeHistory.getReccreated().getTime().toString(), is(history.getReccreated().getTime().toString())); assertThat(storeHistory.getRecupdated(), not(nullValue())); - assertThat(storeHistory.getRecupdated().toString(), is(new Date().toString())); + assertThat(storeHistory.getRecupdated().getTime().toString(), is(Calendar.getInstance().getTime().toString())); } diff --git a/src/src/test/java/ownradio/repository/TrackRepositoryTest.java b/src/src/test/java/ownradio/repository/TrackRepositoryTest.java index 6c7618a..f216043 100644 --- a/src/src/test/java/ownradio/repository/TrackRepositoryTest.java +++ b/src/src/test/java/ownradio/repository/TrackRepositoryTest.java @@ -37,9 +37,9 @@ public class TrackRepositoryTest { public void setUp() throws Exception { user = entityManager.persist(new User()); device = entityManager.persist(new Device(user, "123")); - entityManager.persist(new Track("1", device, "1")); - entityManager.persist(new Track("2", device, "1")); - entityManager.persist(new Track("4", device, "1")); + entityManager.persist(new Track("1", device, "1", 0, "", 0, null, null, null)); + entityManager.persist(new Track("2", device, "1", 0, "", 0, null, null, null)); + entityManager.persist(new Track("4", device, "1", 0, "", 0, null, null, null)); } @Test diff --git a/src/src/test/java/ownradio/web/rest/v2/HistoryControllerTest.java b/src/src/test/java/ownradio/web/rest/v2/HistoryControllerTest.java index b8b425c..e39ca0e 100644 --- a/src/src/test/java/ownradio/web/rest/v2/HistoryControllerTest.java +++ b/src/src/test/java/ownradio/web/rest/v2/HistoryControllerTest.java @@ -1,12 +1,16 @@ package ownradio.web.rest.v2; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.util.JSONPObject; +import com.oracle.webservices.internal.api.message.ContentType; +import org.json.JSONObject; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.web.servlet.MockMvc; import ownradio.domain.Device; @@ -68,10 +72,17 @@ public void saveStatusIsOk() throws Exception { given(this.trackService.getById(TRACK_UUID)).willReturn(track); given(this.deviceService.getById(DEVICE_UUID)).willReturn(device); + JSONObject obj = new JSONObject(); + obj.put("lastListen", "2016-11-28T12:34:56"); + obj.put("isListen", "1"); + obj.put("method", "method"); + mockMvc.perform(post("/api/v2/histories/{deviceId}/{trackId}", DEVICE_UUID, TRACK_UUID) - .param("lastListen", "12/12/2016 11:11:11") - .param("isListen", "1") - .param("method", "method") + .contentType(MediaType.APPLICATION_JSON) + .content(obj.toString()) +// .param("lastListen", "2016-11-28 12:34:56") +// .param("isListen", "1") +// .param("method", "method") ) .andDo(print()) .andExpect( @@ -87,10 +98,17 @@ public void saveStatusIsInternalServerError() throws Exception { doThrow(RuntimeException.class).when(this.historyService).save(any(History.class)); + JSONObject obj = new JSONObject(); + obj.put("lastListen", "2016-11-28T12:34:56"); + obj.put("isListen", "1"); + obj.put("method", "method"); + mockMvc.perform(post("/api/v2/histories/{deviceId}/{trackId}", DEVICE_UUID, TRACK_UUID) - .param("lastListen", "12/12/2016 11:11:11") - .param("isListen", "1") - .param("method", "method") + .contentType(MediaType.APPLICATION_JSON) + .content(obj.toString()) +// .param("lastListen", "2016-11-28 12:34:56") +// .param("isListen", "1") +// .param("method", "method") ) .andDo(print()) .andExpect( diff --git a/src/src/test/java/ownradio/web/rest/v2/TrackControllerTest.java b/src/src/test/java/ownradio/web/rest/v2/TrackControllerTest.java index 138a18f..c4762d4 100644 --- a/src/src/test/java/ownradio/web/rest/v2/TrackControllerTest.java +++ b/src/src/test/java/ownradio/web/rest/v2/TrackControllerTest.java @@ -2,6 +2,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.tomcat.util.http.fileupload.FileUtils; +import org.json.JSONObject; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -66,7 +67,7 @@ public void setUp() throws Exception { device.setRecid(DEVICE_UUID); device.setUser(user); - track = new Track(PATH, device, "---"); + track = new Track(PATH, device, "---", 0, "", 0, null, null, null); String requestParam = "musicFile"; String originalFilename = "test.mp3"; @@ -87,13 +88,21 @@ public void tearDown() throws Exception { public void saveStatusIsOk() throws Exception { given(this.deviceService.getById(DEVICE_UUID)).willReturn(device); + JSONObject obj = new JSONObject(); + obj.put("fileGuid", TRACK_UUID.toString()); + obj.put("fileName", correctFile.getOriginalFilename()); + obj.put("filePath", PATH); + obj.put("deviceId", DEVICE_UUID.toString()); + mockMvc.perform(fileUpload("/api/v2/tracks") .file(correctFile) .accept(MediaType.APPLICATION_JSON_UTF8_VALUE) - .param("fileGuid", TRACK_UUID.toString()) - .param("fileName", correctFile.getOriginalFilename()) - .param("filePath", PATH) - .param("deviceId", DEVICE_UUID.toString()) + .content(obj.toString()) + .contentType(MediaType.MULTIPART_FORM_DATA) +// .param("fileGuid", TRACK_UUID.toString()) +// .param("fileName", correctFile.getOriginalFilename()) +// .param("filePath", PATH) +// .param("deviceId", DEVICE_UUID.toString()) ) .andDo(print()) .andExpect( diff --git a/src/src/test/java/ownradio/web/rest/v3/HistoryControllerV3Test.java b/src/src/test/java/ownradio/web/rest/v3/HistoryControllerV3Test.java new file mode 100644 index 0000000..09f1eff --- /dev/null +++ b/src/src/test/java/ownradio/web/rest/v3/HistoryControllerV3Test.java @@ -0,0 +1,122 @@ +package ownradio.web.rest.v3; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.json.JSONObject; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.web.servlet.MockMvc; +import ownradio.domain.Device; +import ownradio.domain.History; +import ownradio.domain.Track; +import ownradio.domain.User; +import ownradio.service.DeviceService; +import ownradio.service.HistoryService; +import ownradio.service.TrackService; +import ownradio.service.UserService; +import ownradio.web.rest.v2.HistoryController; + +import java.util.UUID; + +import static org.junit.Assert.*; +import static org.mockito.BDDMockito.given; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.doThrow; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +/** + * Created by a.polunina on 28.11.2016. + */ + +@RunWith(SpringRunner.class) +@WebMvcTest(HistoryController.class) +public class HistoryControllerV3Test { + public static final UUID TRACK_UUID = UUID.randomUUID(); + public static final UUID USER_UUID = UUID.randomUUID(); + public static final UUID DEVICE_UUID = UUID.randomUUID(); + + @MockBean + private HistoryService historyService; + + @MockBean + private UserService userService; + + @MockBean + private TrackService trackService; + + @MockBean + private DeviceService deviceService; + + @Autowired + private MockMvc mockMvc; + + private ObjectMapper mapper = new ObjectMapper(); + + private User user; + private Track track; + private Device device; + + @Before + public void setUp() throws Exception { + user = new User(); + track = new Track(); + device = new Device(); + } + + @Test + public void saveStatusIsOk() throws Exception { + given(this.userService.getById(USER_UUID)).willReturn(user); + given(this.trackService.getById(TRACK_UUID)).willReturn(track); + given(this.deviceService.getById(DEVICE_UUID)).willReturn(device); + + JSONObject obj = new JSONObject(); + obj.put("lastListen", "2016-11-28T12:34:56"); + obj.put("isListen", "1"); + obj.put("method", "method"); + + mockMvc.perform(post("/api/v2/histories/{deviceId}/{trackId}", DEVICE_UUID, TRACK_UUID) + .contentType(MediaType.APPLICATION_JSON) + .content(obj.toString()) +// .param("lastListen", "2016-11-28 12:34:56") +// .param("isListen", "1") +// .param("method", "method") + ) + .andDo(print()) + .andExpect( + status().isOk() + ); + } + + @Test + public void saveStatusIsInternalServerError() throws Exception { + given(this.userService.getById(USER_UUID)).willReturn(user); + given(this.trackService.getById(TRACK_UUID)).willReturn(track); + given(this.deviceService.getById(DEVICE_UUID)).willReturn(device); + + doThrow(RuntimeException.class).when(this.historyService).save(any(History.class)); + + JSONObject obj = new JSONObject(); + obj.put("lastListen", "2016-11-28T12:34:56"); + obj.put("isListen", "1"); + obj.put("method", "method"); + + mockMvc.perform(post("/api/v2/histories/{deviceId}/{trackId}", DEVICE_UUID, TRACK_UUID) + .contentType(MediaType.APPLICATION_JSON) + .content(obj.toString()) +// .param("lastListen", "2016-11-28 12:34:56") +// .param("isListen", "1") +// .param("method", "method") + ) + .andDo(print()) + .andExpect( + status().isInternalServerError() + ); + } +} \ No newline at end of file diff --git a/src/src/test/java/ownradio/web/rest/v3/TrackControllerV3Test.java b/src/src/test/java/ownradio/web/rest/v3/TrackControllerV3Test.java new file mode 100644 index 0000000..a4a845b --- /dev/null +++ b/src/src/test/java/ownradio/web/rest/v3/TrackControllerV3Test.java @@ -0,0 +1,191 @@ +//package ownradio.web.rest.v3; +// +//import com.fasterxml.jackson.databind.ObjectMapper; +//import org.apache.tomcat.util.http.fileupload.FileUtils; +//import org.json.JSONObject; +//import org.junit.After; +//import org.junit.Before; +//import org.junit.Test; +//import org.springframework.beans.factory.annotation.Autowired; +//import org.springframework.boot.test.mock.mockito.MockBean; +//import org.springframework.http.MediaType; +//import org.springframework.mock.web.MockMultipartFile; +//import org.springframework.test.web.servlet.MockMvc; +//import ownradio.domain.Device; +//import ownradio.domain.Track; +//import ownradio.domain.User; +//import ownradio.repository.TrackRepository; +//import ownradio.service.DeviceService; +//import ownradio.service.TrackService; +//import ownradio.util.ResourceUtil; +// +//import java.io.File; +//import java.util.UUID; +// +//import static org.hamcrest.core.Is.is; +//import static org.junit.Assert.*; +//import static org.mockito.BDDMockito.given; +//import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.fileUpload; +//import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +//import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +//import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +//import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; +//import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +//import static ownradio.util.ResourceUtil.UPLOAD_DIR; +// +///** +// * Created by a.polunina on 28.11.2016. +// */ +//public class TrackControllerV3Test { +// public static final UUID TRACK_UUID = UUID.randomUUID(); +// public static final UUID USER_UUID = UUID.randomUUID(); +// public static final UUID DEVICE_UUID = UUID.randomUUID(); +// public static final String FILE = TRACK_UUID + ".mp3"; +// public static final String PATH = UPLOAD_DIR + USER_UUID + "/" + FILE; +// +// @MockBean +// private TrackService trackService; +// +// @MockBean +// private DeviceService deviceService; +// +// @MockBean +// TrackRepository trackRepository; +// +// @Autowired +// protected MockMvc mockMvc; +// +// private MockMultipartFile correctFile; +// private MockMultipartFile emptyFile; +// +// private ObjectMapper mapper = new ObjectMapper(); +// +// private User user = new User(); +// private Device device = new Device(); +// private Track track; +// +// @Before +// public void setUp() throws Exception { +// user.setRecid(USER_UUID); +// device.setRecid(DEVICE_UUID); +// device.setUser(user); +// +// track = new Track(PATH, device, "---"); +// +// String requestParam = "musicFile"; +// String originalFilename = "test.mp3"; +// String contentType = "audio/mpeg"; +// +// correctFile = new MockMultipartFile(requestParam, originalFilename, contentType, "Text".getBytes()); +// emptyFile = new MockMultipartFile(requestParam, originalFilename, contentType, "".getBytes()); +// ResourceUtil.save(USER_UUID.toString(), FILE, correctFile); +// } +// +// @After +// public void tearDown() throws Exception { +// FileUtils.deleteDirectory(new File(UPLOAD_DIR)); +// } +// +// @Test +// public void saveStatusIsOk() throws Exception { +// given(this.deviceService.getById(DEVICE_UUID)).willReturn(device); +// +// JSONObject obj = new JSONObject(); +// obj.put("fileGuid", TRACK_UUID.toString()); +// obj.put("fileName", correctFile.getOriginalFilename()); +// obj.put("filePath", PATH); +// obj.put("deviceId", DEVICE_UUID.toString()); +// +// mockMvc.perform(fileUpload("/api/v3/tracks") +// .file(correctFile) +// .accept(MediaType.APPLICATION_JSON_UTF8_VALUE) +// .content(obj.toString()) +// .contentType(MediaType.MULTIPART_FORM_DATA) +//// .param("fileGuid", TRACK_UUID.toString()) +//// .param("fileName", correctFile.getOriginalFilename()) +//// .param("filePath", PATH) +//// .param("deviceId", DEVICE_UUID.toString()) +// ) +// .andDo(print()) +// .andExpect( +// status().isCreated() +// ); +// } +// +// @Test +// public void saveStatusIsBadRequest() throws Exception { +// mockMvc.perform(fileUpload("/api/v3/tracks") +// .file(emptyFile) +// .accept(MediaType.APPLICATION_JSON_UTF8_VALUE) +// .param("fileGuid", TRACK_UUID.toString()) +// .param("fileName", correctFile.getOriginalFilename()) +// .param("filePath", PATH) +// .param("deviceId", DEVICE_UUID.toString()) +// ) +// .andDo(print()) +// .andExpect( +// status().isBadRequest() +// ); +// } +// +// @Test +// public void getTrackStatusIsOk() throws Exception { +// given(this.trackService.getById(TRACK_UUID)).willReturn(track); +// +// mockMvc.perform(get("/api/v3/tracks/{trackId}", TRACK_UUID).accept(MediaType.TEXT_PLAIN)) +// .andDo(print()) +// .andExpect( +// status().isOk() +// ) +// .andExpect( +// header().string("Content-Type", is("audio/mpeg")) +// ) +// .andExpect( +// content().string("Text") +// ); +// } +// +// @Test +// public void getTrackStatusIsNotFound() throws Exception { +// +// given(this.trackService.getById(TRACK_UUID)).willReturn(null); +// +// mockMvc.perform(get("/api/v3/tracks/{trackId}", TRACK_UUID).accept(MediaType.TEXT_PLAIN)) +// .andDo(print()) +// .andExpect( +// status().isNotFound() +// ); +// } +// +// @Test +// public void getNextTrackIdIsOk() throws Exception { +// given(this.trackService.getNextTrackId(DEVICE_UUID)).willReturn(TRACK_UUID); +// +// mockMvc.perform(get("/api/v3/tracks/{deviceId}/next", DEVICE_UUID)) +// .andDo(print()) +// .andExpect( +// status().isOk() +// ) +// .andExpect( +// content().string(mapper.writeValueAsString(TRACK_UUID)) +// ); +// +// } +// +// @Test +// public void getNextTrackIdIsNotFound() throws Exception { +// given(this.trackService.getNextTrackId(DEVICE_UUID)).willReturn(null); +// +// mockMvc.perform(get("/api/v3/tracks/{deviceId}/next", DEVICE_UUID)) +// .andDo(print()) +// .andExpect( +// status().isNotFound() +// ); +// +// } +// @Test +// public void getNextTrackIdV2() throws Exception { +// +// } +// +//} \ No newline at end of file From 636a916e60eab688d9eef35d0d04aea2ddcd5f3c Mon Sep 17 00:00:00 2001 From: Alexandra Polunina Date: Tue, 29 Nov 2016 21:15:33 +0300 Subject: [PATCH 10/44] =?UTF-8?q?=D0=B8=D1=81=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=B8=D0=BB=D0=B0=20=D0=B0=D0=BF=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/README.md | 8 +- .../web/rest/v2/HistoryController.java | 2 +- .../ownradio/web/rest/v2/TrackController.java | 2 +- .../web/rest/v2/HistoryControllerTest.java | 4 +- .../web/rest/v2/TrackControllerTest.java | 12 +- .../web/rest/v3/HistoryControllerV3Test.java | 122 ----------- .../web/rest/v3/TrackControllerV3Test.java | 191 ------------------ 7 files changed, 14 insertions(+), 327 deletions(-) delete mode 100644 src/src/test/java/ownradio/web/rest/v3/HistoryControllerV3Test.java delete mode 100644 src/src/test/java/ownradio/web/rest/v3/TrackControllerV3Test.java diff --git a/src/README.md b/src/README.md index 4f3a4cd..8cb855e 100644 --- a/src/README.md +++ b/src/README.md @@ -18,7 +18,7 @@ Web API ### Загрузка трека на сервер -##### POST /api/v2/tracks +##### POST /v2/tracks * `fileGuid` – UUID трека * `fileName` – имя файла * `filePath` - Локальный путь к файлу на пользовательском устройстве @@ -31,7 +31,7 @@ Web API * `500, "Internal Server Error"` – если произошел сбой на сервере ### Получение трека с сервера -##### GET /api/v2/tracks/{trackId} +##### GET /v2/tracks/{trackId} * `{trackId}` – UUID трека ##### HttpStatus @@ -39,7 +39,7 @@ Web API * `404, "Not Found"` – если трек с таким recid не найден ### Получение следующего трека с сервера -##### GET /api/v2/tracks/{deviceId}/next +##### GET /v2/tracks/{deviceId}/next * `{deviceId}` – UUID девайса ##### HttpStatus @@ -47,7 +47,7 @@ Web API * `404, "Not Found"` – если трек с таким recid не найден ### Сохранение истории треков -##### POST /api/v2/histories/{deviceId}/{trackId} +##### POST /v2/histories/{deviceId}/{trackId} * `{trackId}` – UUID прослушанного трека * `{deviceId}` – UUID устройства где был прослушан трек * `lastListen` - Время последнего прослушивания или пропуска трека для данного пользователя diff --git a/src/src/main/java/ownradio/web/rest/v2/HistoryController.java b/src/src/main/java/ownradio/web/rest/v2/HistoryController.java index cc71f08..b0a000f 100644 --- a/src/src/main/java/ownradio/web/rest/v2/HistoryController.java +++ b/src/src/main/java/ownradio/web/rest/v2/HistoryController.java @@ -30,7 +30,7 @@ */ @Slf4j @RestController -@RequestMapping("/api/v2/histories") +@RequestMapping("/v2/histories") public class HistoryController { private final HistoryService historyService; private final TrackService trackService; diff --git a/src/src/main/java/ownradio/web/rest/v2/TrackController.java b/src/src/main/java/ownradio/web/rest/v2/TrackController.java index c37c9c6..1dbd30f 100644 --- a/src/src/main/java/ownradio/web/rest/v2/TrackController.java +++ b/src/src/main/java/ownradio/web/rest/v2/TrackController.java @@ -22,7 +22,7 @@ */ @Slf4j @RestController -@RequestMapping(value = "/api/v2/tracks") +@RequestMapping(value = "/v2/tracks") public class TrackController { private final TrackService trackService; diff --git a/src/src/test/java/ownradio/web/rest/v2/HistoryControllerTest.java b/src/src/test/java/ownradio/web/rest/v2/HistoryControllerTest.java index e39ca0e..27821e1 100644 --- a/src/src/test/java/ownradio/web/rest/v2/HistoryControllerTest.java +++ b/src/src/test/java/ownradio/web/rest/v2/HistoryControllerTest.java @@ -77,7 +77,7 @@ public void saveStatusIsOk() throws Exception { obj.put("isListen", "1"); obj.put("method", "method"); - mockMvc.perform(post("/api/v2/histories/{deviceId}/{trackId}", DEVICE_UUID, TRACK_UUID) + mockMvc.perform(post("/v2/histories/{deviceId}/{trackId}", DEVICE_UUID, TRACK_UUID) .contentType(MediaType.APPLICATION_JSON) .content(obj.toString()) // .param("lastListen", "2016-11-28 12:34:56") @@ -103,7 +103,7 @@ public void saveStatusIsInternalServerError() throws Exception { obj.put("isListen", "1"); obj.put("method", "method"); - mockMvc.perform(post("/api/v2/histories/{deviceId}/{trackId}", DEVICE_UUID, TRACK_UUID) + mockMvc.perform(post("/v2/histories/{deviceId}/{trackId}", DEVICE_UUID, TRACK_UUID) .contentType(MediaType.APPLICATION_JSON) .content(obj.toString()) // .param("lastListen", "2016-11-28 12:34:56") diff --git a/src/src/test/java/ownradio/web/rest/v2/TrackControllerTest.java b/src/src/test/java/ownradio/web/rest/v2/TrackControllerTest.java index c4762d4..f5c8bfa 100644 --- a/src/src/test/java/ownradio/web/rest/v2/TrackControllerTest.java +++ b/src/src/test/java/ownradio/web/rest/v2/TrackControllerTest.java @@ -94,7 +94,7 @@ public void saveStatusIsOk() throws Exception { obj.put("filePath", PATH); obj.put("deviceId", DEVICE_UUID.toString()); - mockMvc.perform(fileUpload("/api/v2/tracks") + mockMvc.perform(fileUpload("/v2/tracks") .file(correctFile) .accept(MediaType.APPLICATION_JSON_UTF8_VALUE) .content(obj.toString()) @@ -112,7 +112,7 @@ public void saveStatusIsOk() throws Exception { @Test public void saveStatusIsBadRequest() throws Exception { - mockMvc.perform(fileUpload("/api/v2/tracks") + mockMvc.perform(fileUpload("/v2/tracks") .file(emptyFile) .accept(MediaType.APPLICATION_JSON_UTF8_VALUE) .param("fileGuid", TRACK_UUID.toString()) @@ -130,7 +130,7 @@ public void saveStatusIsBadRequest() throws Exception { public void getTrackStatusIsOk() throws Exception { given(this.trackService.getById(TRACK_UUID)).willReturn(track); - mockMvc.perform(get("/api/v2/tracks/{trackId}", TRACK_UUID).accept(MediaType.TEXT_PLAIN)) + mockMvc.perform(get("/v2/tracks/{trackId}", TRACK_UUID).accept(MediaType.TEXT_PLAIN)) .andDo(print()) .andExpect( status().isOk() @@ -148,7 +148,7 @@ public void getTrackStatusIsNotFound() throws Exception { given(this.trackService.getById(TRACK_UUID)).willReturn(null); - mockMvc.perform(get("/api/v2/tracks/{trackId}", TRACK_UUID).accept(MediaType.TEXT_PLAIN)) + mockMvc.perform(get("/v2/tracks/{trackId}", TRACK_UUID).accept(MediaType.TEXT_PLAIN)) .andDo(print()) .andExpect( status().isNotFound() @@ -159,7 +159,7 @@ public void getTrackStatusIsNotFound() throws Exception { public void getNextTrackIdIsOk() throws Exception { given(this.trackService.getNextTrackId(DEVICE_UUID)).willReturn(TRACK_UUID); - mockMvc.perform(get("/api/v2/tracks/{deviceId}/next", DEVICE_UUID)) + mockMvc.perform(get("/v2/tracks/{deviceId}/next", DEVICE_UUID)) .andDo(print()) .andExpect( status().isOk() @@ -174,7 +174,7 @@ public void getNextTrackIdIsOk() throws Exception { public void getNextTrackIdIsNotFound() throws Exception { given(this.trackService.getNextTrackId(DEVICE_UUID)).willReturn(null); - mockMvc.perform(get("/api/v2/tracks/{deviceId}/next", DEVICE_UUID)) + mockMvc.perform(get("/v2/tracks/{deviceId}/next", DEVICE_UUID)) .andDo(print()) .andExpect( status().isNotFound() diff --git a/src/src/test/java/ownradio/web/rest/v3/HistoryControllerV3Test.java b/src/src/test/java/ownradio/web/rest/v3/HistoryControllerV3Test.java deleted file mode 100644 index 09f1eff..0000000 --- a/src/src/test/java/ownradio/web/rest/v3/HistoryControllerV3Test.java +++ /dev/null @@ -1,122 +0,0 @@ -package ownradio.web.rest.v3; - -import com.fasterxml.jackson.databind.ObjectMapper; -import org.json.JSONObject; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; -import org.springframework.boot.test.mock.mockito.MockBean; -import org.springframework.http.MediaType; -import org.springframework.test.context.junit4.SpringRunner; -import org.springframework.test.web.servlet.MockMvc; -import ownradio.domain.Device; -import ownradio.domain.History; -import ownradio.domain.Track; -import ownradio.domain.User; -import ownradio.service.DeviceService; -import ownradio.service.HistoryService; -import ownradio.service.TrackService; -import ownradio.service.UserService; -import ownradio.web.rest.v2.HistoryController; - -import java.util.UUID; - -import static org.junit.Assert.*; -import static org.mockito.BDDMockito.given; -import static org.mockito.Matchers.any; -import static org.mockito.Mockito.doThrow; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -/** - * Created by a.polunina on 28.11.2016. - */ - -@RunWith(SpringRunner.class) -@WebMvcTest(HistoryController.class) -public class HistoryControllerV3Test { - public static final UUID TRACK_UUID = UUID.randomUUID(); - public static final UUID USER_UUID = UUID.randomUUID(); - public static final UUID DEVICE_UUID = UUID.randomUUID(); - - @MockBean - private HistoryService historyService; - - @MockBean - private UserService userService; - - @MockBean - private TrackService trackService; - - @MockBean - private DeviceService deviceService; - - @Autowired - private MockMvc mockMvc; - - private ObjectMapper mapper = new ObjectMapper(); - - private User user; - private Track track; - private Device device; - - @Before - public void setUp() throws Exception { - user = new User(); - track = new Track(); - device = new Device(); - } - - @Test - public void saveStatusIsOk() throws Exception { - given(this.userService.getById(USER_UUID)).willReturn(user); - given(this.trackService.getById(TRACK_UUID)).willReturn(track); - given(this.deviceService.getById(DEVICE_UUID)).willReturn(device); - - JSONObject obj = new JSONObject(); - obj.put("lastListen", "2016-11-28T12:34:56"); - obj.put("isListen", "1"); - obj.put("method", "method"); - - mockMvc.perform(post("/api/v2/histories/{deviceId}/{trackId}", DEVICE_UUID, TRACK_UUID) - .contentType(MediaType.APPLICATION_JSON) - .content(obj.toString()) -// .param("lastListen", "2016-11-28 12:34:56") -// .param("isListen", "1") -// .param("method", "method") - ) - .andDo(print()) - .andExpect( - status().isOk() - ); - } - - @Test - public void saveStatusIsInternalServerError() throws Exception { - given(this.userService.getById(USER_UUID)).willReturn(user); - given(this.trackService.getById(TRACK_UUID)).willReturn(track); - given(this.deviceService.getById(DEVICE_UUID)).willReturn(device); - - doThrow(RuntimeException.class).when(this.historyService).save(any(History.class)); - - JSONObject obj = new JSONObject(); - obj.put("lastListen", "2016-11-28T12:34:56"); - obj.put("isListen", "1"); - obj.put("method", "method"); - - mockMvc.perform(post("/api/v2/histories/{deviceId}/{trackId}", DEVICE_UUID, TRACK_UUID) - .contentType(MediaType.APPLICATION_JSON) - .content(obj.toString()) -// .param("lastListen", "2016-11-28 12:34:56") -// .param("isListen", "1") -// .param("method", "method") - ) - .andDo(print()) - .andExpect( - status().isInternalServerError() - ); - } -} \ No newline at end of file diff --git a/src/src/test/java/ownradio/web/rest/v3/TrackControllerV3Test.java b/src/src/test/java/ownradio/web/rest/v3/TrackControllerV3Test.java deleted file mode 100644 index a4a845b..0000000 --- a/src/src/test/java/ownradio/web/rest/v3/TrackControllerV3Test.java +++ /dev/null @@ -1,191 +0,0 @@ -//package ownradio.web.rest.v3; -// -//import com.fasterxml.jackson.databind.ObjectMapper; -//import org.apache.tomcat.util.http.fileupload.FileUtils; -//import org.json.JSONObject; -//import org.junit.After; -//import org.junit.Before; -//import org.junit.Test; -//import org.springframework.beans.factory.annotation.Autowired; -//import org.springframework.boot.test.mock.mockito.MockBean; -//import org.springframework.http.MediaType; -//import org.springframework.mock.web.MockMultipartFile; -//import org.springframework.test.web.servlet.MockMvc; -//import ownradio.domain.Device; -//import ownradio.domain.Track; -//import ownradio.domain.User; -//import ownradio.repository.TrackRepository; -//import ownradio.service.DeviceService; -//import ownradio.service.TrackService; -//import ownradio.util.ResourceUtil; -// -//import java.io.File; -//import java.util.UUID; -// -//import static org.hamcrest.core.Is.is; -//import static org.junit.Assert.*; -//import static org.mockito.BDDMockito.given; -//import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.fileUpload; -//import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -//import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; -//import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; -//import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; -//import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -//import static ownradio.util.ResourceUtil.UPLOAD_DIR; -// -///** -// * Created by a.polunina on 28.11.2016. -// */ -//public class TrackControllerV3Test { -// public static final UUID TRACK_UUID = UUID.randomUUID(); -// public static final UUID USER_UUID = UUID.randomUUID(); -// public static final UUID DEVICE_UUID = UUID.randomUUID(); -// public static final String FILE = TRACK_UUID + ".mp3"; -// public static final String PATH = UPLOAD_DIR + USER_UUID + "/" + FILE; -// -// @MockBean -// private TrackService trackService; -// -// @MockBean -// private DeviceService deviceService; -// -// @MockBean -// TrackRepository trackRepository; -// -// @Autowired -// protected MockMvc mockMvc; -// -// private MockMultipartFile correctFile; -// private MockMultipartFile emptyFile; -// -// private ObjectMapper mapper = new ObjectMapper(); -// -// private User user = new User(); -// private Device device = new Device(); -// private Track track; -// -// @Before -// public void setUp() throws Exception { -// user.setRecid(USER_UUID); -// device.setRecid(DEVICE_UUID); -// device.setUser(user); -// -// track = new Track(PATH, device, "---"); -// -// String requestParam = "musicFile"; -// String originalFilename = "test.mp3"; -// String contentType = "audio/mpeg"; -// -// correctFile = new MockMultipartFile(requestParam, originalFilename, contentType, "Text".getBytes()); -// emptyFile = new MockMultipartFile(requestParam, originalFilename, contentType, "".getBytes()); -// ResourceUtil.save(USER_UUID.toString(), FILE, correctFile); -// } -// -// @After -// public void tearDown() throws Exception { -// FileUtils.deleteDirectory(new File(UPLOAD_DIR)); -// } -// -// @Test -// public void saveStatusIsOk() throws Exception { -// given(this.deviceService.getById(DEVICE_UUID)).willReturn(device); -// -// JSONObject obj = new JSONObject(); -// obj.put("fileGuid", TRACK_UUID.toString()); -// obj.put("fileName", correctFile.getOriginalFilename()); -// obj.put("filePath", PATH); -// obj.put("deviceId", DEVICE_UUID.toString()); -// -// mockMvc.perform(fileUpload("/api/v3/tracks") -// .file(correctFile) -// .accept(MediaType.APPLICATION_JSON_UTF8_VALUE) -// .content(obj.toString()) -// .contentType(MediaType.MULTIPART_FORM_DATA) -//// .param("fileGuid", TRACK_UUID.toString()) -//// .param("fileName", correctFile.getOriginalFilename()) -//// .param("filePath", PATH) -//// .param("deviceId", DEVICE_UUID.toString()) -// ) -// .andDo(print()) -// .andExpect( -// status().isCreated() -// ); -// } -// -// @Test -// public void saveStatusIsBadRequest() throws Exception { -// mockMvc.perform(fileUpload("/api/v3/tracks") -// .file(emptyFile) -// .accept(MediaType.APPLICATION_JSON_UTF8_VALUE) -// .param("fileGuid", TRACK_UUID.toString()) -// .param("fileName", correctFile.getOriginalFilename()) -// .param("filePath", PATH) -// .param("deviceId", DEVICE_UUID.toString()) -// ) -// .andDo(print()) -// .andExpect( -// status().isBadRequest() -// ); -// } -// -// @Test -// public void getTrackStatusIsOk() throws Exception { -// given(this.trackService.getById(TRACK_UUID)).willReturn(track); -// -// mockMvc.perform(get("/api/v3/tracks/{trackId}", TRACK_UUID).accept(MediaType.TEXT_PLAIN)) -// .andDo(print()) -// .andExpect( -// status().isOk() -// ) -// .andExpect( -// header().string("Content-Type", is("audio/mpeg")) -// ) -// .andExpect( -// content().string("Text") -// ); -// } -// -// @Test -// public void getTrackStatusIsNotFound() throws Exception { -// -// given(this.trackService.getById(TRACK_UUID)).willReturn(null); -// -// mockMvc.perform(get("/api/v3/tracks/{trackId}", TRACK_UUID).accept(MediaType.TEXT_PLAIN)) -// .andDo(print()) -// .andExpect( -// status().isNotFound() -// ); -// } -// -// @Test -// public void getNextTrackIdIsOk() throws Exception { -// given(this.trackService.getNextTrackId(DEVICE_UUID)).willReturn(TRACK_UUID); -// -// mockMvc.perform(get("/api/v3/tracks/{deviceId}/next", DEVICE_UUID)) -// .andDo(print()) -// .andExpect( -// status().isOk() -// ) -// .andExpect( -// content().string(mapper.writeValueAsString(TRACK_UUID)) -// ); -// -// } -// -// @Test -// public void getNextTrackIdIsNotFound() throws Exception { -// given(this.trackService.getNextTrackId(DEVICE_UUID)).willReturn(null); -// -// mockMvc.perform(get("/api/v3/tracks/{deviceId}/next", DEVICE_UUID)) -// .andDo(print()) -// .andExpect( -// status().isNotFound() -// ); -// -// } -// @Test -// public void getNextTrackIdV2() throws Exception { -// -// } -// -//} \ No newline at end of file From bbb585bfd92e347bd652dac81848bcccfeb0b86f Mon Sep 17 00:00:00 2001 From: Alexandra Polunina Date: Tue, 29 Nov 2016 22:42:13 +0300 Subject: [PATCH 11/44] =?UTF-8?q?=D0=A3=D0=B4=D0=B0=D0=BB=D0=B8=D0=BB?= =?UTF-8?q?=D0=B0=20=D0=B8=D0=BC=D0=BF=D0=BE=D1=80=D1=82=20=D0=BF=D0=B0?= =?UTF-8?q?=D0=BA=D0=B5=D1=82=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../test/java/ownradio/web/rest/v2/HistoryControllerTest.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/src/test/java/ownradio/web/rest/v2/HistoryControllerTest.java b/src/src/test/java/ownradio/web/rest/v2/HistoryControllerTest.java index 27821e1..be5ae70 100644 --- a/src/src/test/java/ownradio/web/rest/v2/HistoryControllerTest.java +++ b/src/src/test/java/ownradio/web/rest/v2/HistoryControllerTest.java @@ -1,8 +1,6 @@ package ownradio.web.rest.v2; import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.util.JSONPObject; -import com.oracle.webservices.internal.api.message.ContentType; import org.json.JSONObject; import org.junit.Before; import org.junit.Test; From aec79bf02f123507f42d735084d035a8f0af3716 Mon Sep 17 00:00:00 2001 From: Alexandra Polunina Date: Wed, 30 Nov 2016 12:00:52 +0300 Subject: [PATCH 12/44] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8?= =?UTF-8?q?=D0=BB=D0=B0=20=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D1=83=20=D1=81=20?= =?UTF-8?q?=D1=82=D0=B5=D0=B3=D0=B0=D0=BC=D0=B8,=20=D0=BE=D1=82=D0=BA?= =?UTF-8?q?=D0=B0=D1=82=D0=B8=D0=BB=D0=B0=20=D0=B2=D0=B5=D1=80=D1=81=D0=B8?= =?UTF-8?q?=D1=8E=20spring?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pom.xml | 8 +- .../main/java/ownradio/domain/NextTrack.java | 2 + .../service/impl/TrackServiceImpl.java | 17 ++ ...ControllerV3.java => TrackController.java} | 68 +++++-- .../main/resources/data/postgresql/schema.sql | 192 +++++++++--------- 5 files changed, 177 insertions(+), 110 deletions(-) rename src/src/main/java/ownradio/web/rest/v3/{TrackControllerV3.java => TrackController.java} (56%) diff --git a/src/pom.xml b/src/pom.xml index 539354c..2c81444 100644 --- a/src/pom.xml +++ b/src/pom.xml @@ -14,7 +14,7 @@ org.springframework.boot spring-boot-starter-parent - 1.4.2.RELEASE + 1.4.0.RELEASE @@ -58,6 +58,12 @@ spring-boot-starter-test test + + + com.mpatric + mp3agic + 0.8.3 + diff --git a/src/src/main/java/ownradio/domain/NextTrack.java b/src/src/main/java/ownradio/domain/NextTrack.java index 1dc01fb..ddf7c73 100644 --- a/src/src/main/java/ownradio/domain/NextTrack.java +++ b/src/src/main/java/ownradio/domain/NextTrack.java @@ -4,6 +4,7 @@ import lombok.Setter; import org.hibernate.annotations.Type; +import javax.persistence.Entity; import java.util.UUID; /** @@ -11,6 +12,7 @@ */ @Getter @Setter +//@Entity public class NextTrack { private Integer methodid; diff --git a/src/src/main/java/ownradio/service/impl/TrackServiceImpl.java b/src/src/main/java/ownradio/service/impl/TrackServiceImpl.java index 29b77c4..f254928 100644 --- a/src/src/main/java/ownradio/service/impl/TrackServiceImpl.java +++ b/src/src/main/java/ownradio/service/impl/TrackServiceImpl.java @@ -63,4 +63,21 @@ public void save(Track track, MultipartFile file) { storeTrack.setPath(filePath); } + +// @Override +// @Transactional +// public void update(Track track) { +// boolean result = trackRepository.registerTrack(track.getRecid(), track.getLocaldevicepathupload(), track.getPath(), track.getDevice().getRecid()); +// if (!result) { +// throw new RuntimeException(); +// } +// +// Track storeTrack = trackRepository.findOne(track.getRecid()); +// +// String dirName = storeTrack.getDevice().getUser().getRecid().toString(); +// String fileName = storeTrack.getRecid() + "." + StringUtils.getFilenameExtension(file.getOriginalFilename()); +// String filePath = ResourceUtil.save(dirName, fileName, file); +// +// storeTrack.setPath(filePath); +// } } diff --git a/src/src/main/java/ownradio/web/rest/v3/TrackControllerV3.java b/src/src/main/java/ownradio/web/rest/v3/TrackController.java similarity index 56% rename from src/src/main/java/ownradio/web/rest/v3/TrackControllerV3.java rename to src/src/main/java/ownradio/web/rest/v3/TrackController.java index ca9ff0b..e653a8b 100644 --- a/src/src/main/java/ownradio/web/rest/v3/TrackControllerV3.java +++ b/src/src/main/java/ownradio/web/rest/v3/TrackController.java @@ -1,5 +1,7 @@ package ownradio.web.rest.v3; +import com.mpatric.mp3agic.ID3v1; +import com.mpatric.mp3agic.Mp3File; import lombok.Data; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; @@ -15,21 +17,24 @@ import ownradio.service.TrackService; import ownradio.util.ResourceUtil; +import javax.sound.sampled.*; +import java.io.File; +import java.io.IOException; import java.util.*; /** * Created by a.polunina on 28.11.2016. */ @Slf4j -@RestController +@RestController("TrackControllerV3") @RequestMapping(value = "/v3/tracks") -public class TrackControllerV3 { +public class TrackController { private final TrackService trackService; private final TrackRepository trackRepository; @Autowired - public TrackControllerV3(TrackService trackService, TrackRepository trackRepository) { + public TrackController(TrackService trackService, TrackRepository trackRepository) { this.trackService = trackService; this.trackRepository = trackRepository; } @@ -58,7 +63,7 @@ public Track getTrack() { } @RequestMapping(method = RequestMethod.POST) - public ResponseEntity save(ownradio.web.rest.v3.TrackControllerV3.TrackDTO trackDTO) { + public ResponseEntity save(TrackDTO trackDTO) { if (trackDTO.getMusicFile().isEmpty()) { return new ResponseEntity(HttpStatus.BAD_REQUEST); } @@ -95,20 +100,57 @@ private HttpHeaders getHttpAudioHeaders() { public ResponseEntity getNextTrack(@PathVariable UUID deviceId) { NextTrack nextTrack = trackService.getNextTrackIdV2(deviceId); UUID trackId = nextTrack.getTrackid(); + Map trackInfo = new HashMap<>(); if (trackId != null) { - Track track = trackRepository.findOne(trackId); - log.info("{} {} {}", track.getRecname(), track.getArtist(), track.getLength()); - Map trackInfo = new HashMap<>(); - trackInfo.put("id", trackId.toString()); - trackInfo.put("length", String.valueOf(track.getLength())); - trackInfo.put("name", track.getRecname()); - trackInfo.put("artist", track.getArtist()); - trackInfo.put("methodid", nextTrack.getMethodid().toString()); - return new ResponseEntity<>(trackInfo, HttpStatus.OK); + try { + Track track = trackRepository.findOne(trackId); +// if (track.getIsfilledinfo() == 0 || track.getIsfilledinfo() == null) { + Mp3File mp3File = new Mp3File(track.getPath()); + if (mp3File.hasId3v1Tag()) { + ID3v1 id3v1Tag = mp3File.getId3v1Tag(); + track.setRecname(id3v1Tag.getTitle()); + track.setArtist(id3v1Tag.getArtist()); + track.setLength((int) mp3File.getLengthInSeconds()); + } +// trackService.save(track); +// } + trackInfo.put("id", trackId.toString()); + trackInfo.put("length", String.valueOf(track.getLength())); + trackInfo.put("name", track.getRecname()); + trackInfo.put("artist", track.getArtist()); + trackInfo.put("methodid", nextTrack.getMethodid().toString()); + + return new ResponseEntity<>(trackInfo, HttpStatus.OK); + }catch (Exception ex){ + log.debug("{}", ex.getMessage()); + return new ResponseEntity<>(HttpStatus.NOT_FOUND); + } } else { return new ResponseEntity<>(HttpStatus.NOT_FOUND); } } + private static long getDurationWithMp3Spi(File file) throws UnsupportedAudioFileException, IOException { + try { + AudioFileFormat fileFormat = AudioSystem.getAudioFileFormat(file); + Map properties = ((AudioFileFormat) fileFormat).properties(); + String key = "duration"; + return ((Long) properties.get("duration")) / 1000; + +// AudioFileFormat fileFormat = AudioSystem.getAudioFileFormat(file); +// +// Map properties = ((AudioFileFormat) fileFormat).properties(); +// String key = "duration"; +// Long microseconds = (Long) properties.get(key); +// int mili = (int) (microseconds / 1000); +// int sec = (mili / 1000) % 60; +// int min = (mili / 1000) / 60; +// return microseconds; + }catch (Exception ex){ + log.debug("{}", ex.getMessage()); + } + return 0; + } + } \ No newline at end of file diff --git a/src/src/main/resources/data/postgresql/schema.sql b/src/src/main/resources/data/postgresql/schema.sql index 918be2c..5f88e76 100644 --- a/src/src/main/resources/data/postgresql/schema.sql +++ b/src/src/main/resources/data/postgresql/schema.sql @@ -123,88 +123,88 @@ END; ' LANGUAGE plpgsql; - -CREATE OR REPLACE FUNCTION getnexttrackid_v2(i_deviceid uuid, OUT o_methodid integer, OUT o_trackid uuid) - AS -' -DECLARE - i_userid uuid = i_deviceid; - rnd integer = (select trunc(random() * 10)); -BEGIN - -- Добавляем устройство, если его еще не существует - -- Если ID устройства еще нет в БД - IF NOT EXISTS(SELECT recid - FROM devices - WHERE recid = i_deviceid) - THEN - - -- Добавляем нового пользователя - INSERT INTO users (recid, recname, reccreated) SELECT - i_userid, - ''New user recname'', - now(); - - -- Добавляем новое устройство - INSERT INTO devices (recid, userid, recname, reccreated) SELECT - i_deviceid, - i_userid, - ''New device recname'', - now(); - ELSE - SELECT (SELECT userid - FROM devices - WHERE recid = i_deviceid - LIMIT 1) - INTO i_userid; - END IF; - - -- Выбираем следующий трек - - -- В 9/10 случаях выбираем трек из треков пользователя (добавленных им или прослушанных до конца) - -- с положительным рейтингом, за исключением прослушанных за последние сутки - IF (rnd > 1) - THEN - o_methodid = 2; - o_trackid=(SELECT trackid - FROM ratings - WHERE userid = i_userid - AND lastlisten < localtimestamp - interval ''1 day'' - AND ratingsum >= 0 - ORDER BY RANDOM() - LIMIT 1); - - -- Если такой трек найден - выход из функции, возврат найденного значения - IF FOUND - THEN RETURN; - END IF; - END IF; - - -- В 1/10 случае выбираем случайный трек из ни разу не прослушанных пользователем треков - o_methodid = 3; - o_trackid=(SELECT recid - FROM tracks - WHERE recid NOT IN - (SELECT trackid - FROM ratings - WHERE userid = i_userid) - ORDER BY RANDOM() - LIMIT 1); - - -- Если такой трек найден - выход из функции, возврат найденного значения - IF FOUND - THEN RETURN; - END IF; - - -- Если предыдущие запросы вернули null, выбираем случайный трек - o_methodid = 1; - o_trackid=(SELECT recid - FROM tracks - ORDER BY RANDOM() - LIMIT 1); - RETURN; -END; -' -LANGUAGE plpgsql; +-- +-- CREATE OR REPLACE FUNCTION getnexttrackid_v2(i_deviceid uuid, OUT o_methodid integer, OUT o_trackid uuid) +-- AS +-- ' +-- DECLARE +-- i_userid uuid = i_deviceid; +-- rnd integer = (select trunc(random() * 10)); +-- BEGIN +-- -- Добавляем устройство, если его еще не существует +-- -- Если ID устройства еще нет в БД +-- IF NOT EXISTS(SELECT recid +-- FROM devices +-- WHERE recid = i_deviceid) +-- THEN +-- +-- -- Добавляем нового пользователя +-- INSERT INTO users (recid, recname, reccreated) SELECT +-- i_userid, +-- ''New user recname'', +-- now(); +-- +-- -- Добавляем новое устройство +-- INSERT INTO devices (recid, userid, recname, reccreated) SELECT +-- i_deviceid, +-- i_userid, +-- ''New device recname'', +-- now(); +-- ELSE +-- SELECT (SELECT userid +-- FROM devices +-- WHERE recid = i_deviceid +-- LIMIT 1) +-- INTO i_userid; +-- END IF; +-- +-- -- Выбираем следующий трек +-- +-- -- В 9/10 случаях выбираем трек из треков пользователя (добавленных им или прослушанных до конца) +-- -- с положительным рейтингом, за исключением прослушанных за последние сутки +-- IF (rnd > 1) +-- THEN +-- o_methodid = 2; +-- o_trackid=(SELECT trackid +-- FROM ratings +-- WHERE userid = i_userid +-- AND lastlisten < localtimestamp - interval ''1 day'' +-- AND ratingsum >= 0 +-- ORDER BY RANDOM() +-- LIMIT 1); +-- +-- -- Если такой трек найден - выход из функции, возврат найденного значения +-- IF FOUND +-- THEN RETURN; +-- END IF; +-- END IF; +-- +-- -- В 1/10 случае выбираем случайный трек из ни разу не прослушанных пользователем треков +-- o_methodid = 3; +-- o_trackid=(SELECT recid +-- FROM tracks +-- WHERE recid NOT IN +-- (SELECT trackid +-- FROM ratings +-- WHERE userid = i_userid) +-- ORDER BY RANDOM() +-- LIMIT 1); +-- +-- -- Если такой трек найден - выход из функции, возврат найденного значения +-- IF FOUND +-- THEN RETURN; +-- END IF; +-- +-- -- Если предыдущие запросы вернули null, выбираем случайный трек +-- o_methodid = 1; +-- o_trackid=(SELECT recid +-- FROM tracks +-- ORDER BY RANDOM() +-- LIMIT 1); +-- RETURN; +-- END; +-- ' +-- LANGUAGE plpgsql; -- CREATE TYPE myintuuid AS ( -- methodid INTEGER, @@ -217,17 +217,17 @@ LANGUAGE plpgsql; -- trackid CHARACTER VARYING -- ); -- -CREATE OR REPLACE FUNCTION public.getnexttrack_string(i_deviceid uuid) - RETURNS CHARACTER VARYING AS -' -DECLARE - tmpres myintuuid; - res myintstring; -BEGIN - tmpres = getnexttrackid_v2(i_deviceid); - res.methodid = tmpres.methodid; - res.trackid = CAST ((tmpres.trackid) AS CHARACTER VARYING); - RETURN res.trackid; -END; -' -LANGUAGE plpgsql; \ No newline at end of file +-- CREATE OR REPLACE FUNCTION public.getnexttrack_string(i_deviceid uuid) +-- RETURNS CHARACTER VARYING AS +-- ' +-- DECLARE +-- tmpres myintuuid; +-- res myintstring; +-- BEGIN +-- tmpres = getnexttrackid_v2(i_deviceid); +-- res.methodid = tmpres.methodid; +-- res.trackid = CAST ((tmpres.trackid) AS CHARACTER VARYING); +-- RETURN res.trackid; +-- END; +-- ' +-- LANGUAGE plpgsql; \ No newline at end of file From 2bf96a7e7c14b37424cd2c154111582345160dcb Mon Sep 17 00:00:00 2001 From: Alexandra Polunina Date: Thu, 1 Dec 2016 13:08:12 +0300 Subject: [PATCH 13/44] =?UTF-8?q?=D0=9F=D1=80=D0=BE=D0=B1=D0=BB=D0=B5?= =?UTF-8?q?=D0=BC=D0=B0=20=D1=81=20objects?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/ownradio/domain/NextTrack.java | 6 +- .../ownradio/repository/TrackRepository.java | 7 +- .../service/impl/TrackServiceImpl.java | 53 +++-- .../ownradio/web/rest/v3/TrackController.java | 55 ++--- .../main/resources/data/postgresql/schema.sql | 197 ++++++++---------- 5 files changed, 146 insertions(+), 172 deletions(-) diff --git a/src/src/main/java/ownradio/domain/NextTrack.java b/src/src/main/java/ownradio/domain/NextTrack.java index ddf7c73..182072b 100644 --- a/src/src/main/java/ownradio/domain/NextTrack.java +++ b/src/src/main/java/ownradio/domain/NextTrack.java @@ -5,6 +5,7 @@ import org.hibernate.annotations.Type; import javax.persistence.Entity; +import javax.persistence.Id; import java.util.UUID; /** @@ -12,10 +13,7 @@ */ @Getter @Setter -//@Entity public class NextTrack { - private Integer methodid; - - @Type(type="pg-uuid") private UUID trackid; + private Integer methodid; } diff --git a/src/src/main/java/ownradio/repository/TrackRepository.java b/src/src/main/java/ownradio/repository/TrackRepository.java index 56d772f..8402056 100644 --- a/src/src/main/java/ownradio/repository/TrackRepository.java +++ b/src/src/main/java/ownradio/repository/TrackRepository.java @@ -5,7 +5,9 @@ import ownradio.domain.NextTrack; import ownradio.domain.Track; +import javax.xml.transform.Result; import java.sql.ResultSet; +import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.UUID; @@ -19,9 +21,8 @@ public interface TrackRepository extends JpaRepository { @Query(value = "select getnexttrackid_string(?1)", nativeQuery = true) UUID getNextTrackId(UUID deviceId); - @Query(value = "select getnexttrackid_string(?1)", nativeQuery = true) -// @Query(value = "select getnexttrack_string(?1)", nativeQuery = true) - UUID getNextTrackV2(UUID deviceId); + @Query(value = "select * from getnexttrackid_v2(?1)", nativeQuery = true) + Object[] getNextTrackV2(UUID deviceId); @Query(value = "select registertrack(?1, ?2, ?3, ?4)", nativeQuery = true) boolean registerTrack(UUID trackId, String localDevicePathUpload, String path, UUID deviceId); diff --git a/src/src/main/java/ownradio/service/impl/TrackServiceImpl.java b/src/src/main/java/ownradio/service/impl/TrackServiceImpl.java index f254928..d1bd8ed 100644 --- a/src/src/main/java/ownradio/service/impl/TrackServiceImpl.java +++ b/src/src/main/java/ownradio/service/impl/TrackServiceImpl.java @@ -11,8 +11,7 @@ import ownradio.service.TrackService; import ownradio.util.ResourceUtil; -import java.sql.ResultSet; -import java.util.Collection; +import java.util.ArrayList; import java.util.List; import java.util.UUID; @@ -20,10 +19,13 @@ public class TrackServiceImpl implements TrackService { private final TrackRepository trackRepository; +// private final NextTrackRepository nextTrackRepository; + @Autowired public TrackServiceImpl(TrackRepository trackRepository) { this.trackRepository = trackRepository; +// this.nextTrackRepository = nextTrackRepository; } @Override @@ -42,8 +44,34 @@ public UUID getNextTrackId(UUID deviceId) { @Transactional public NextTrack getNextTrackIdV2(UUID deviceId) { NextTrack nextTrack = new NextTrack(); - nextTrack.setTrackid(trackRepository.getNextTrackV2(deviceId)); - nextTrack.setMethodid(1);//!!! + Object[] objects = trackRepository.getNextTrackV2(deviceId); + List obj = (List)objects[0]; + obj.get(0); +// for(Object tp : objects){ +// nextTrack.setTrackid(((NextTrack)tp).getTrackid()); +// nextTrack.setMethodid(((NextTrack)tp).getMethodid()) ; +// } +//// +// objects.get(0) + + Object a = objects[0]; +// a = objects[0][1]; + + + for (Object o : objects) { + + if (o instanceof Integer) { + nextTrack.setMethodid((int) o); + } else if (o instanceof String) { + nextTrack.setTrackid(UUID.fromString((String) o)); + } + } +// nextTrack.setTrackid(UUID.fromString(objects[0][0].toString())); +// nextTrack.setMethodid((int)objects[0][1]); + //String str = trackRepository.getNextTrackV2(deviceId); +// nextTrack.setTrackid(UUID.fromString(str.substring(0,36))); +// nextTrack.setMethodid(Integer.parseInt(str.substring(37,38))); +// nextTrack =trackRepository.getNextTrackV2(deviceId); return nextTrack; } @@ -63,21 +91,4 @@ public void save(Track track, MultipartFile file) { storeTrack.setPath(filePath); } - -// @Override -// @Transactional -// public void update(Track track) { -// boolean result = trackRepository.registerTrack(track.getRecid(), track.getLocaldevicepathupload(), track.getPath(), track.getDevice().getRecid()); -// if (!result) { -// throw new RuntimeException(); -// } -// -// Track storeTrack = trackRepository.findOne(track.getRecid()); -// -// String dirName = storeTrack.getDevice().getUser().getRecid().toString(); -// String fileName = storeTrack.getRecid() + "." + StringUtils.getFilenameExtension(file.getOriginalFilename()); -// String filePath = ResourceUtil.save(dirName, fileName, file); -// -// storeTrack.setPath(filePath); -// } } diff --git a/src/src/main/java/ownradio/web/rest/v3/TrackController.java b/src/src/main/java/ownradio/web/rest/v3/TrackController.java index e653a8b..d705025 100644 --- a/src/src/main/java/ownradio/web/rest/v3/TrackController.java +++ b/src/src/main/java/ownradio/web/rest/v3/TrackController.java @@ -1,6 +1,8 @@ package ownradio.web.rest.v3; import com.mpatric.mp3agic.ID3v1; +import com.mpatric.mp3agic.ID3v1Tag; +import com.mpatric.mp3agic.ID3v2; import com.mpatric.mp3agic.Mp3File; import lombok.Data; import lombok.extern.slf4j.Slf4j; @@ -18,8 +20,7 @@ import ownradio.util.ResourceUtil; import javax.sound.sampled.*; -import java.io.File; -import java.io.IOException; +import java.io.*; import java.util.*; /** @@ -99,23 +100,28 @@ private HttpHeaders getHttpAudioHeaders() { @RequestMapping(value = "/{deviceId}/next", method = RequestMethod.GET) public ResponseEntity getNextTrack(@PathVariable UUID deviceId) { NextTrack nextTrack = trackService.getNextTrackIdV2(deviceId); - UUID trackId = nextTrack.getTrackid(); Map trackInfo = new HashMap<>(); - if (trackId != null) { + if (nextTrack.getTrackid() != null) { try { - Track track = trackRepository.findOne(trackId); -// if (track.getIsfilledinfo() == 0 || track.getIsfilledinfo() == null) { + Track track = trackRepository.findOne(nextTrack.getTrackid()); + + if(track.getIsfilledinfo() == null || track.getIsfilledinfo() != 1) + { Mp3File mp3File = new Mp3File(track.getPath()); + + track.setLength((int) mp3File.getLengthInSeconds()); + track.setSize((int)mp3File.getLength() / 1024);//size in kilobytes + if (mp3File.hasId3v1Tag()) { - ID3v1 id3v1Tag = mp3File.getId3v1Tag(); - track.setRecname(id3v1Tag.getTitle()); - track.setArtist(id3v1Tag.getArtist()); - track.setLength((int) mp3File.getLengthInSeconds()); + ID3v2 id3v2Tag2 = mp3File.getId3v2Tag(); + track.setRecname(id3v2Tag2.getTitle()); + track.setArtist(id3v2Tag2.getArtist()); } -// trackService.save(track); -// } - trackInfo.put("id", trackId.toString()); + track.setIsfilledinfo(1); + trackRepository.saveAndFlush(track); + } + trackInfo.put("id", nextTrack.getTrackid().toString()); trackInfo.put("length", String.valueOf(track.getLength())); trackInfo.put("name", track.getRecname()); trackInfo.put("artist", track.getArtist()); @@ -130,27 +136,4 @@ public ResponseEntity getNextTrack(@PathVariable UUID deviceId) { return new ResponseEntity<>(HttpStatus.NOT_FOUND); } } - - private static long getDurationWithMp3Spi(File file) throws UnsupportedAudioFileException, IOException { - try { - AudioFileFormat fileFormat = AudioSystem.getAudioFileFormat(file); - Map properties = ((AudioFileFormat) fileFormat).properties(); - String key = "duration"; - return ((Long) properties.get("duration")) / 1000; - -// AudioFileFormat fileFormat = AudioSystem.getAudioFileFormat(file); -// -// Map properties = ((AudioFileFormat) fileFormat).properties(); -// String key = "duration"; -// Long microseconds = (Long) properties.get(key); -// int mili = (int) (microseconds / 1000); -// int sec = (mili / 1000) % 60; -// int min = (mili / 1000) / 60; -// return microseconds; - }catch (Exception ex){ - log.debug("{}", ex.getMessage()); - } - return 0; - } - } \ No newline at end of file diff --git a/src/src/main/resources/data/postgresql/schema.sql b/src/src/main/resources/data/postgresql/schema.sql index 5f88e76..f8889db 100644 --- a/src/src/main/resources/data/postgresql/schema.sql +++ b/src/src/main/resources/data/postgresql/schema.sql @@ -123,111 +123,92 @@ END; ' LANGUAGE plpgsql; --- --- CREATE OR REPLACE FUNCTION getnexttrackid_v2(i_deviceid uuid, OUT o_methodid integer, OUT o_trackid uuid) --- AS --- ' --- DECLARE --- i_userid uuid = i_deviceid; --- rnd integer = (select trunc(random() * 10)); --- BEGIN --- -- Добавляем устройство, если его еще не существует --- -- Если ID устройства еще нет в БД --- IF NOT EXISTS(SELECT recid --- FROM devices --- WHERE recid = i_deviceid) --- THEN --- --- -- Добавляем нового пользователя --- INSERT INTO users (recid, recname, reccreated) SELECT --- i_userid, --- ''New user recname'', --- now(); --- --- -- Добавляем новое устройство --- INSERT INTO devices (recid, userid, recname, reccreated) SELECT --- i_deviceid, --- i_userid, --- ''New device recname'', --- now(); --- ELSE --- SELECT (SELECT userid --- FROM devices --- WHERE recid = i_deviceid --- LIMIT 1) --- INTO i_userid; --- END IF; --- --- -- Выбираем следующий трек --- --- -- В 9/10 случаях выбираем трек из треков пользователя (добавленных им или прослушанных до конца) --- -- с положительным рейтингом, за исключением прослушанных за последние сутки --- IF (rnd > 1) --- THEN --- o_methodid = 2; --- o_trackid=(SELECT trackid --- FROM ratings --- WHERE userid = i_userid --- AND lastlisten < localtimestamp - interval ''1 day'' --- AND ratingsum >= 0 --- ORDER BY RANDOM() --- LIMIT 1); --- --- -- Если такой трек найден - выход из функции, возврат найденного значения --- IF FOUND --- THEN RETURN; --- END IF; --- END IF; --- --- -- В 1/10 случае выбираем случайный трек из ни разу не прослушанных пользователем треков --- o_methodid = 3; --- o_trackid=(SELECT recid --- FROM tracks --- WHERE recid NOT IN --- (SELECT trackid --- FROM ratings --- WHERE userid = i_userid) --- ORDER BY RANDOM() --- LIMIT 1); --- --- -- Если такой трек найден - выход из функции, возврат найденного значения --- IF FOUND --- THEN RETURN; --- END IF; --- --- -- Если предыдущие запросы вернули null, выбираем случайный трек --- o_methodid = 1; --- o_trackid=(SELECT recid --- FROM tracks --- ORDER BY RANDOM() --- LIMIT 1); --- RETURN; --- END; --- ' --- LANGUAGE plpgsql; - --- CREATE TYPE myintuuid AS ( --- methodid INTEGER, --- trackid CHARACTER VARYING --- ); - - --- CREATE TYPE myintstring AS ( --- methodid INTEGER, --- trackid CHARACTER VARYING --- ); --- --- CREATE OR REPLACE FUNCTION public.getnexttrack_string(i_deviceid uuid) --- RETURNS CHARACTER VARYING AS --- ' --- DECLARE --- tmpres myintuuid; --- res myintstring; --- BEGIN --- tmpres = getnexttrackid_v2(i_deviceid); --- res.methodid = tmpres.methodid; --- res.trackid = CAST ((tmpres.trackid) AS CHARACTER VARYING); --- RETURN res.trackid; --- END; --- ' --- LANGUAGE plpgsql; \ No newline at end of file + +CREATE OR REPLACE FUNCTION getnexttrackid_v2(i_deviceid uuid) + RETURNS TABLE( + track character varying + , methodid integer) +AS +' +DECLARE + i_userid uuid = i_deviceid; + rnd integer = (select trunc(random() * 10)); + o_methodid integer; +BEGIN + -- Добавляем устройство, если его еще не существует + -- Если ID устройства еще нет в БД + IF NOT EXISTS(SELECT recid + FROM devices + WHERE recid = i_deviceid) + THEN + + -- Добавляем нового пользователя + INSERT INTO users (recid, recname, reccreated) SELECT + i_userid, + ''New user recname'', + now(); + + -- Добавляем новое устройство + INSERT INTO devices (recid, userid, recname, reccreated) SELECT + i_deviceid, + i_userid, + ''New device recname'', + now(); + ELSE + SELECT (SELECT userid + FROM devices + WHERE recid = i_deviceid + LIMIT 1) + INTO i_userid; + END IF; + + -- Выбираем следующий трек + + -- В 9/10 случаях выбираем трек из треков пользователя (добавленных им или прослушанных до конца) + -- с положительным рейтингом, за исключением прослушанных за последние сутки + IF (rnd > 1) + THEN + o_methodid = 2; + RETURN QUERY + SELECT CAST ((trackid) AS CHARACTER VARYING) AS track, o_methodid AS methodid + FROM ratings + WHERE userid = i_userid + AND lastlisten < localtimestamp - interval ''1 day'' + AND ratingsum >= 0 + ORDER BY RANDOM() + LIMIT 1; + + -- Если такой трек найден - выход из функции, возврат найденного значения + IF FOUND + THEN RETURN; + END IF; + END IF; + + -- В 1/10 случае выбираем случайный трек из ни разу не прослушанных пользователем треков + o_methodid = 3; + RETURN QUERY + SELECT CAST ((recid) AS CHARACTER VARYING) AS track, o_methodid AS methodid + FROM tracks + WHERE recid NOT IN + (SELECT trackid + FROM ratings + WHERE userid = i_userid) + ORDER BY RANDOM() + LIMIT 1; + + -- Если такой трек найден - выход из функции, возврат найденного значения + IF FOUND + THEN RETURN; + END IF; + + -- Если предыдущие запросы вернули null, выбираем случайный трек + o_methodid = 1; + RETURN QUERY + SELECT CAST ((recid) AS CHARACTER VARYING) AS track, o_methodid AS methodid + FROM tracks + ORDER BY RANDOM() + LIMIT 1; + RETURN; +END; +' +LANGUAGE plpgsql; \ No newline at end of file From 366861a1a517e28a51cb65cdd997c5a3b358b138 Mon Sep 17 00:00:00 2001 From: Alexandra Polunina Date: Thu, 1 Dec 2016 15:59:10 +0300 Subject: [PATCH 14/44] =?UTF-8?q?=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D0=B0=20=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D0=B0?= =?UTF-8?q?=20=D1=81=20=D1=82=D0=B5=D0=B3=D0=B0=D0=BC=D0=B8,=20=D0=B4?= =?UTF-8?q?=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D0=BE=20=D1=81=D0=BE?= =?UTF-8?q?=D1=85=D1=80=D0=B0=D0=BD=D0=B5=D0=BD=D0=B8=D0=B5=20=D1=80=D0=B0?= =?UTF-8?q?=D0=B7=D0=BC=D0=B5=D1=80=D0=B0=20=D0=B4=D0=B8=D1=81=D0=BA=D0=B0?= =?UTF-8?q?.=20=D0=A1=D0=B4=D0=B5=D0=BB=D0=B0=D0=BD=D0=BE=20=D0=BD=D0=BE?= =?UTF-8?q?=D1=80=D0=BC=D0=B0=D0=BB=D1=8C=D0=BD=D0=BE=D0=B5=20=D0=BF=D0=BE?= =?UTF-8?q?=D0=BB=D1=83=D1=87=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=BC=D0=B5=D1=82?= =?UTF-8?q?=D0=BE=D0=B4=D0=B0=20=D0=B8=D0=B7=20=D1=85=D1=80=D0=B0=D0=BD?= =?UTF-8?q?=D0=B8=D0=BC=D0=BE=D0=B9=20=D0=BF=D1=80=D0=BE=D1=86=D0=B5=D0=B4?= =?UTF-8?q?=D1=83=D1=80=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/ownradio/domain/NextTrack.java | 3 -- .../ownradio/repository/TrackRepository.java | 7 +--- .../service/impl/TrackServiceImpl.java | 35 ++----------------- ...ntrollerV3.java => HistoryController.java} | 6 ++-- .../ownradio/web/rest/v3/TrackController.java | 16 +++++---- 5 files changed, 16 insertions(+), 51 deletions(-) rename src/src/main/java/ownradio/web/rest/v3/{HistoryControllerV3.java => HistoryController.java} (90%) diff --git a/src/src/main/java/ownradio/domain/NextTrack.java b/src/src/main/java/ownradio/domain/NextTrack.java index 182072b..a9f49cc 100644 --- a/src/src/main/java/ownradio/domain/NextTrack.java +++ b/src/src/main/java/ownradio/domain/NextTrack.java @@ -2,10 +2,7 @@ import lombok.Getter; import lombok.Setter; -import org.hibernate.annotations.Type; -import javax.persistence.Entity; -import javax.persistence.Id; import java.util.UUID; /** diff --git a/src/src/main/java/ownradio/repository/TrackRepository.java b/src/src/main/java/ownradio/repository/TrackRepository.java index 8402056..629cb88 100644 --- a/src/src/main/java/ownradio/repository/TrackRepository.java +++ b/src/src/main/java/ownradio/repository/TrackRepository.java @@ -2,13 +2,8 @@ import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; -import ownradio.domain.NextTrack; import ownradio.domain.Track; -import javax.xml.transform.Result; -import java.sql.ResultSet; -import java.util.ArrayList; -import java.util.Collection; import java.util.List; import java.util.UUID; @@ -22,7 +17,7 @@ public interface TrackRepository extends JpaRepository { UUID getNextTrackId(UUID deviceId); @Query(value = "select * from getnexttrackid_v2(?1)", nativeQuery = true) - Object[] getNextTrackV2(UUID deviceId); + List getNextTrackV2(UUID deviceId); @Query(value = "select registertrack(?1, ?2, ?3, ?4)", nativeQuery = true) boolean registerTrack(UUID trackId, String localDevicePathUpload, String path, UUID deviceId); diff --git a/src/src/main/java/ownradio/service/impl/TrackServiceImpl.java b/src/src/main/java/ownradio/service/impl/TrackServiceImpl.java index d1bd8ed..6676ced 100644 --- a/src/src/main/java/ownradio/service/impl/TrackServiceImpl.java +++ b/src/src/main/java/ownradio/service/impl/TrackServiceImpl.java @@ -11,7 +11,6 @@ import ownradio.service.TrackService; import ownradio.util.ResourceUtil; -import java.util.ArrayList; import java.util.List; import java.util.UUID; @@ -19,13 +18,10 @@ public class TrackServiceImpl implements TrackService { private final TrackRepository trackRepository; -// private final NextTrackRepository nextTrackRepository; - @Autowired public TrackServiceImpl(TrackRepository trackRepository) { this.trackRepository = trackRepository; -// this.nextTrackRepository = nextTrackRepository; } @Override @@ -44,34 +40,9 @@ public UUID getNextTrackId(UUID deviceId) { @Transactional public NextTrack getNextTrackIdV2(UUID deviceId) { NextTrack nextTrack = new NextTrack(); - Object[] objects = trackRepository.getNextTrackV2(deviceId); - List obj = (List)objects[0]; - obj.get(0); -// for(Object tp : objects){ -// nextTrack.setTrackid(((NextTrack)tp).getTrackid()); -// nextTrack.setMethodid(((NextTrack)tp).getMethodid()) ; -// } -//// -// objects.get(0) - - Object a = objects[0]; -// a = objects[0][1]; - - - for (Object o : objects) { - - if (o instanceof Integer) { - nextTrack.setMethodid((int) o); - } else if (o instanceof String) { - nextTrack.setTrackid(UUID.fromString((String) o)); - } - } -// nextTrack.setTrackid(UUID.fromString(objects[0][0].toString())); -// nextTrack.setMethodid((int)objects[0][1]); - //String str = trackRepository.getNextTrackV2(deviceId); -// nextTrack.setTrackid(UUID.fromString(str.substring(0,36))); -// nextTrack.setMethodid(Integer.parseInt(str.substring(37,38))); -// nextTrack =trackRepository.getNextTrackV2(deviceId); + List objects = trackRepository.getNextTrackV2(deviceId); + nextTrack.setTrackid(UUID.fromString((String) objects.get(0)[0])); + nextTrack.setMethodid((Integer) objects.get(0)[1]); return nextTrack; } diff --git a/src/src/main/java/ownradio/web/rest/v3/HistoryControllerV3.java b/src/src/main/java/ownradio/web/rest/v3/HistoryController.java similarity index 90% rename from src/src/main/java/ownradio/web/rest/v3/HistoryControllerV3.java rename to src/src/main/java/ownradio/web/rest/v3/HistoryController.java index 99d66ed..f177382 100644 --- a/src/src/main/java/ownradio/web/rest/v3/HistoryControllerV3.java +++ b/src/src/main/java/ownradio/web/rest/v3/HistoryController.java @@ -21,15 +21,15 @@ * Created by a.polunina on 28.11.2016. */ @Slf4j -@RestController +@RestController("HistoryControllerV3") @RequestMapping("/v3/histories") -public class HistoryControllerV3 { +public class HistoryController { private final HistoryService historyService; private final TrackService trackService; private final DeviceService deviceService; @Autowired - public HistoryControllerV3(HistoryService historyService, TrackService trackService, DeviceService deviceService) { + public HistoryController(HistoryService historyService, TrackService trackService, DeviceService deviceService) { this.historyService = historyService; this.trackService = trackService; this.deviceService = deviceService; diff --git a/src/src/main/java/ownradio/web/rest/v3/TrackController.java b/src/src/main/java/ownradio/web/rest/v3/TrackController.java index d705025..832312e 100644 --- a/src/src/main/java/ownradio/web/rest/v3/TrackController.java +++ b/src/src/main/java/ownradio/web/rest/v3/TrackController.java @@ -1,7 +1,6 @@ package ownradio.web.rest.v3; import com.mpatric.mp3agic.ID3v1; -import com.mpatric.mp3agic.ID3v1Tag; import com.mpatric.mp3agic.ID3v2; import com.mpatric.mp3agic.Mp3File; import lombok.Data; @@ -19,8 +18,6 @@ import ownradio.service.TrackService; import ownradio.util.ResourceUtil; -import javax.sound.sampled.*; -import java.io.*; import java.util.*; /** @@ -113,12 +110,17 @@ public ResponseEntity getNextTrack(@PathVariable UUID deviceId) { track.setLength((int) mp3File.getLengthInSeconds()); track.setSize((int)mp3File.getLength() / 1024);//size in kilobytes - if (mp3File.hasId3v1Tag()) { + if (mp3File.hasId3v2Tag()) { ID3v2 id3v2Tag2 = mp3File.getId3v2Tag(); - track.setRecname(id3v2Tag2.getTitle()); - track.setArtist(id3v2Tag2.getArtist()); + track.setRecname(id3v2Tag2.getTitle().replaceAll("\u0000", "")); + track.setArtist(id3v2Tag2.getArtist().replaceAll("\u0000", "")); + track.setIsfilledinfo(1); + }else if (mp3File.hasId3v1Tag()){ + ID3v1 id3v1Tag1 = mp3File.getId3v1Tag(); + track.setRecname(id3v1Tag1.getTitle().replaceAll("\u0000", "")); + track.setArtist(id3v1Tag1.getArtist().replaceAll("\u0000", "")); + track.setIsfilledinfo(1); } - track.setIsfilledinfo(1); trackRepository.saveAndFlush(track); } trackInfo.put("id", nextTrack.getTrackid().toString()); From b4a883da68f7fcff2c035fa6682c799dd3cc331a Mon Sep 17 00:00:00 2001 From: Alexandra Polunina Date: Thu, 1 Dec 2016 16:09:28 +0300 Subject: [PATCH 15/44] =?UTF-8?q?=D0=97=D0=B0=D0=BC=D0=B5=D0=BD=D0=B8?= =?UTF-8?q?=D0=BB=D0=B0=20=D1=81=D0=BE=D1=85=D1=80=D0=B0=D0=BD=D0=B5=D0=BD?= =?UTF-8?q?=D0=B8=D0=B5=20method=20=D0=BD=D0=B0=20methodid=3D2=20=D0=B2=20?= =?UTF-8?q?=D1=85=D1=80=D0=B0=D0=BD=D0=B8=D0=BC=D0=BA=D0=B5=20=D1=80=D0=B5?= =?UTF-8?q?=D0=B3=D0=B8=D1=81=D1=82=D1=80=D0=B0=D1=86=D0=B8=D0=B8=20=D1=82?= =?UTF-8?q?=D1=80=D0=B5=D0=BA=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/src/main/resources/data/postgresql/schema.sql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/src/main/resources/data/postgresql/schema.sql b/src/src/main/resources/data/postgresql/schema.sql index f8889db..136039d 100644 --- a/src/src/main/resources/data/postgresql/schema.sql +++ b/src/src/main/resources/data/postgresql/schema.sql @@ -111,8 +111,8 @@ BEGIN VALUES (i_trackid, i_localdevicepathupload, i_path, i_deviceid, now()); -- Добавляем запись о прослушивании трека в таблицу истории прослушивания - INSERT INTO histories (recid, deviceid, trackid, isListen, lastListen, method, reccreated) - VALUES (i_historyid, i_deviceid, i_trackid, 1, now(), ''method'', now()); + INSERT INTO histories (recid, deviceid, trackid, isListen, lastListen, methodid, reccreated) + VALUES (i_historyid, i_deviceid, i_trackid, 1, now(), 2, now()); -- Добавляем запись в таблицу рейтингов INSERT INTO ratings (recid, userid, trackid, lastListen, ratingsum, reccreated) From 9ccd1fcf91d231ee9fea9177b4c3c43860c596ba Mon Sep 17 00:00:00 2001 From: Alexandra Polunina Date: Thu, 1 Dec 2016 18:41:44 +0300 Subject: [PATCH 16/44] =?UTF-8?q?=D0=A3=D0=B1=D1=80=D0=B0=D0=BB=D0=B0=20?= =?UTF-8?q?=D0=B8=D0=B7=20api=20upload=20=D0=BF=D0=B0=D1=80=D0=B0=D0=BC?= =?UTF-8?q?=D0=B5=D1=82=D1=80=20fileName,=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2?= =?UTF-8?q?=D0=B8=D0=BB=D0=B0=20=D0=B7=D0=BD=D0=B0=D1=87=D0=B5=D0=BD=D0=B8?= =?UTF-8?q?=D1=8F=20=D0=BF=D0=BE=20=D1=83=D0=BC=D0=BE=D0=BB=D1=87=D0=B0?= =?UTF-8?q?=D0=BD=D0=B8=D1=8E=20=D0=B4=D0=BB=D1=8F=20title=20=D0=B8=20arti?= =?UTF-8?q?st?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ownradio/web/rest/v3/TrackController.java | 48 +++++++++++++++---- 1 file changed, 38 insertions(+), 10 deletions(-) diff --git a/src/src/main/java/ownradio/web/rest/v3/TrackController.java b/src/src/main/java/ownradio/web/rest/v3/TrackController.java index 832312e..b202a6d 100644 --- a/src/src/main/java/ownradio/web/rest/v3/TrackController.java +++ b/src/src/main/java/ownradio/web/rest/v3/TrackController.java @@ -51,7 +51,6 @@ public Track getTrack() { Track track = new Track(); track.setRecid(fileGuid); - track.setRecname(fileName); track.setDevice(device); track.setPath("---"); track.setLocaldevicepathupload(filePath); @@ -98,7 +97,10 @@ private HttpHeaders getHttpAudioHeaders() { public ResponseEntity getNextTrack(@PathVariable UUID deviceId) { NextTrack nextTrack = trackService.getNextTrackIdV2(deviceId); Map trackInfo = new HashMap<>(); - + String artist = null; + String title = null; + boolean artistFlag = false; + boolean titleFlag = false; if (nextTrack.getTrackid() != null) { try { Track track = trackRepository.findOne(nextTrack.getTrackid()); @@ -112,21 +114,47 @@ public ResponseEntity getNextTrack(@PathVariable UUID deviceId) { if (mp3File.hasId3v2Tag()) { ID3v2 id3v2Tag2 = mp3File.getId3v2Tag(); - track.setRecname(id3v2Tag2.getTitle().replaceAll("\u0000", "")); - track.setArtist(id3v2Tag2.getArtist().replaceAll("\u0000", "")); - track.setIsfilledinfo(1); + title = id3v2Tag2.getTitle(); + artist = id3v2Tag2.getArtist(); + +// track.setRecname(id3v2Tag2.getTitle().replaceAll("\u0000", "")); +// track.setArtist(id3v2Tag2.getArtist().replaceAll("\u0000", "")); +// track.setIsfilledinfo(1); }else if (mp3File.hasId3v1Tag()){ ID3v1 id3v1Tag1 = mp3File.getId3v1Tag(); - track.setRecname(id3v1Tag1.getTitle().replaceAll("\u0000", "")); - track.setArtist(id3v1Tag1.getArtist().replaceAll("\u0000", "")); - track.setIsfilledinfo(1); + title = id3v1Tag1.getTitle(); + artist = id3v1Tag1.getArtist(); + +// track.setRecname(id3v1Tag1.getTitle().replaceAll("\u0000", "")); +// track.setArtist(id3v1Tag1.getArtist().replaceAll("\u0000", "")); +// track.setIsfilledinfo(1); } + + if(title != null && !title.equals("null") && !title.isEmpty()){ + track.setRecname(title.replaceAll("\u0000", "")); + titleFlag = true; + }else + titleFlag = false; + if(artist != null && !artist.equals("null") && !artist.isEmpty()){ + track.setArtist(artist.replaceAll("\u0000", "")); + artistFlag = true; + }else + artistFlag = false; + + if(artistFlag && titleFlag) + track.setIsfilledinfo(1); trackRepository.saveAndFlush(track); } trackInfo.put("id", nextTrack.getTrackid().toString()); trackInfo.put("length", String.valueOf(track.getLength())); - trackInfo.put("name", track.getRecname()); - trackInfo.put("artist", track.getArtist()); + if(track.getRecname() != null && !track.getRecname().isEmpty() && !track.getRecname().equals("null")) + trackInfo.put("name", track.getRecname()); + else + trackInfo.put("name", "No name"); + if(track.getArtist() != null && !track.getArtist().isEmpty() && !track.getArtist().equals("null")) + trackInfo.put("artist", track.getArtist()); + else + trackInfo.put("artist", "NetVox Lab"); trackInfo.put("methodid", nextTrack.getMethodid().toString()); return new ResponseEntity<>(trackInfo, HttpStatus.OK); From 1de4d1c86d17720342a875035a516387333068c2 Mon Sep 17 00:00:00 2001 From: Alexandra Polunina Date: Mon, 5 Dec 2016 17:32:28 +0300 Subject: [PATCH 17/44] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8?= =?UTF-8?q?=D0=BB=D0=B0=20=D0=BF=D1=80=D0=BE=D1=86=D0=B5=D0=B4=D1=83=D1=80?= =?UTF-8?q?=D1=8B=20getnexttrack=5Fv3=20=D0=B8=20getnexttrack=20=20(=D0=B4?= =?UTF-8?q?=D0=BB=D1=8F=20=D0=B1=D1=8B=D1=81=D1=82=D1=80=D0=BE=D0=B9=20?= =?UTF-8?q?=D0=B7=D0=B0=D0=BC=D0=B5=D0=BD=D1=8B=20=D0=BF=D1=80=D0=BE=D1=86?= =?UTF-8?q?=D0=B5=D0=B4=D1=83=D1=80).=20=D0=94=D0=BE=D0=B1=D0=B0=D0=B2?= =?UTF-8?q?=D0=B8=D0=BB=D0=B0=20=D0=BF=D1=80=D0=BE=D0=BF=D1=83=D1=81=D0=BA?= =?UTF-8?q?=20=D0=BA=D0=BE=D1=80=D0=BE=D1=82=D0=BA=D0=B8=D1=85=20=D1=82?= =?UTF-8?q?=D1=80=D0=B5=D0=BA=D0=BE=D0=B2.=20=D0=92=D1=8B=D0=B4=D0=B5?= =?UTF-8?q?=D0=BB=D0=B8=D0=BB=D0=B0=20=D0=B2=20=D1=84=D1=83=D0=BD=D0=BA?= =?UTF-8?q?=D1=86=D0=B8=D1=8E=20=D1=81=D0=BE=D1=85=D1=80=D0=B0=D0=BD=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=20=D0=B8=D0=BD=D1=84=D0=BE=D1=80=D0=BC=D0=B0?= =?UTF-8?q?=D1=86=D0=B8=D0=B8=20=D0=BE=20=D1=82=D1=80=D0=B5=D0=BA=D0=B5,?= =?UTF-8?q?=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8=D0=BB=D0=B0=20=D0=B2?= =?UTF-8?q?=D1=8B=D0=B7=D0=BE=D0=B2=20=D1=8D=D1=82=D0=BE=D0=B9=20=D1=84?= =?UTF-8?q?=D1=83=D0=BD=D0=BA=D1=86=D0=B8=D0=B8=20=D0=BF=D1=80=20uploade?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/README.md | 54 +++++ .../ownradio/repository/TrackRepository.java | 3 +- .../java/ownradio/service/TrackService.java | 2 + .../service/impl/TrackServiceImpl.java | 47 +++++ .../ownradio/web/rest/v3/TrackController.java | 58 ++---- .../main/resources/data/postgresql/schema.sql | 185 +++++++++++++----- 6 files changed, 251 insertions(+), 98 deletions(-) diff --git a/src/README.md b/src/README.md index 8cb855e..d663e2b 100644 --- a/src/README.md +++ b/src/README.md @@ -57,3 +57,57 @@ Web API ##### HttpStatus * `200, "OK"` – если все ок * `500, "Internal Server Error"` – если произошел сбой на сервере + +Web API v3 +--- + +### Загрузка трека на сервер + +##### POST /v3/tracks +* `fileGuid` – UUID трека +* `filePath` - Полный локальный путь к файлу на пользовательском устройстве, включая имя файла (String) +* `deviceId` – UUID device +* `musicFile` – прикрепленный файл + +##### HttpStatus +* `400, "Bad Request"` - Если пользователь ввел некорректные данные +* `201, "Created"` – если все ок +* `500, "Internal Server Error"` – если произошел сбой на сервере + +### Получение трека с сервера +##### GET /v3/tracks/{trackId} +* `{trackId}` – UUID трека + +##### HttpStatus +* `200, "OK"` – в теле ответа будет лежать трек +* `404, "Not Found"` – если трек с таким recid не найден + +### Получение следующего трека с сервера +##### GET /v3/tracks/{deviceId}/next +* `{deviceId}` – UUID девайса + +##### Response +Content-Type →application/json;charset=UTF-8 +{ + "artist": "Artist", + "length": "duration", + "name": "Title", + "methodid": "1", + "id": "00000000-0000-0000-0000-000000000000" +} + +##### HttpStatus +* `200, "OK"` – в теле ответа будет лежать UUID трека +* `404, "Not Found"` – если трек с таким recid не найден + +### Сохранение истории треков +##### POST /v3/histories/{deviceId}/{trackId} +* `{trackId}` – UUID прослушанного трека +* `{deviceId}` – UUID устройства где был прослушан трек +* `lastListen` - Время последнего прослушивания или пропуска трека для данного пользователя ("yyyy-MM-ddTHH:mm:ss") +* `isListen` - Признак прослушан ли трек до конца: 1 - прослушан, -1 – нет (int) +* `methodid` - ID метода выбора трека (int), равен значению, полученному при получении данных для следующего трека + +##### HttpStatus +* `200, "OK"` – если все ок +* `500, "Internal Server Error"` – если произошел сбой на сервере diff --git a/src/src/main/java/ownradio/repository/TrackRepository.java b/src/src/main/java/ownradio/repository/TrackRepository.java index 629cb88..e525152 100644 --- a/src/src/main/java/ownradio/repository/TrackRepository.java +++ b/src/src/main/java/ownradio/repository/TrackRepository.java @@ -16,7 +16,8 @@ public interface TrackRepository extends JpaRepository { @Query(value = "select getnexttrackid_string(?1)", nativeQuery = true) UUID getNextTrackId(UUID deviceId); - @Query(value = "select * from getnexttrackid_v2(?1)", nativeQuery = true) + @Query(value = "select * from getnexttrack(?1)", nativeQuery = true) +// @Query(value = "select * from getnexttrackid_v2(?1)", nativeQuery = true) List getNextTrackV2(UUID deviceId); @Query(value = "select registertrack(?1, ?2, ?3, ?4)", nativeQuery = true) diff --git a/src/src/main/java/ownradio/service/TrackService.java b/src/src/main/java/ownradio/service/TrackService.java index c94d0b0..8bad137 100644 --- a/src/src/main/java/ownradio/service/TrackService.java +++ b/src/src/main/java/ownradio/service/TrackService.java @@ -22,4 +22,6 @@ public interface TrackService { NextTrack getNextTrackIdV2(UUID deviceId); void save(Track track, MultipartFile file); + + void setTrackInfo(UUID trackid); } diff --git a/src/src/main/java/ownradio/service/impl/TrackServiceImpl.java b/src/src/main/java/ownradio/service/impl/TrackServiceImpl.java index 6676ced..5baa869 100644 --- a/src/src/main/java/ownradio/service/impl/TrackServiceImpl.java +++ b/src/src/main/java/ownradio/service/impl/TrackServiceImpl.java @@ -1,5 +1,8 @@ package ownradio.service.impl; +import com.mpatric.mp3agic.ID3v1; +import com.mpatric.mp3agic.ID3v2; +import com.mpatric.mp3agic.Mp3File; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -62,4 +65,48 @@ public void save(Track track, MultipartFile file) { storeTrack.setPath(filePath); } + + @Override + @Transactional + public void setTrackInfo(UUID trackid) { + String artist = null; + String title = null; + boolean artistFlag = false; + boolean titleFlag = false; + if (trackid != null) { + try { + Track track = trackRepository.findOne(trackid); + Mp3File mp3File = new Mp3File(track.getPath()); + + track.setLength((int) mp3File.getLengthInSeconds());//duration track + track.setSize((int) mp3File.getLength() / 1024);//size in kilobytes + + if (mp3File.hasId3v2Tag()) { + ID3v2 id3v2Tag2 = mp3File.getId3v2Tag(); + title = id3v2Tag2.getTitle(); + artist = id3v2Tag2.getArtist(); + } else if (mp3File.hasId3v1Tag()) { + ID3v1 id3v1Tag1 = mp3File.getId3v1Tag(); + title = id3v1Tag1.getTitle(); + artist = id3v1Tag1.getArtist(); + } + + if (title != null && !title.equals("null") && !title.isEmpty()) { + track.setRecname(title.replaceAll("\u0000", "")); + titleFlag = true; + } else + titleFlag = false; + if (artist != null && !artist.equals("null") && !artist.isEmpty()) { + track.setArtist(artist.replaceAll("\u0000", "")); + artistFlag = true; + } else + artistFlag = false; + + if (artistFlag && titleFlag) + track.setIsfilledinfo(1); + trackRepository.saveAndFlush(track); + } catch (Exception ex) { + } + } + } } diff --git a/src/src/main/java/ownradio/web/rest/v3/TrackController.java b/src/src/main/java/ownradio/web/rest/v3/TrackController.java index b202a6d..fa9a36c 100644 --- a/src/src/main/java/ownradio/web/rest/v3/TrackController.java +++ b/src/src/main/java/ownradio/web/rest/v3/TrackController.java @@ -1,8 +1,5 @@ package ownradio.web.rest.v3; -import com.mpatric.mp3agic.ID3v1; -import com.mpatric.mp3agic.ID3v2; -import com.mpatric.mp3agic.Mp3File; import lombok.Data; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; @@ -67,7 +64,7 @@ public ResponseEntity save(TrackDTO trackDTO) { try { trackService.save(trackDTO.getTrack(), trackDTO.getMusicFile()); - + trackService.setTrackInfo(trackDTO.getTrack().getRecid()); return new ResponseEntity(HttpStatus.CREATED); } catch (Exception e) { return new ResponseEntity(HttpStatus.INTERNAL_SERVER_ERROR); @@ -104,62 +101,29 @@ public ResponseEntity getNextTrack(@PathVariable UUID deviceId) { if (nextTrack.getTrackid() != null) { try { Track track = trackRepository.findOne(nextTrack.getTrackid()); - if(track.getIsfilledinfo() == null || track.getIsfilledinfo() != 1) - { - Mp3File mp3File = new Mp3File(track.getPath()); - - track.setLength((int) mp3File.getLengthInSeconds()); - track.setSize((int)mp3File.getLength() / 1024);//size in kilobytes - - if (mp3File.hasId3v2Tag()) { - ID3v2 id3v2Tag2 = mp3File.getId3v2Tag(); - title = id3v2Tag2.getTitle(); - artist = id3v2Tag2.getArtist(); - -// track.setRecname(id3v2Tag2.getTitle().replaceAll("\u0000", "")); -// track.setArtist(id3v2Tag2.getArtist().replaceAll("\u0000", "")); -// track.setIsfilledinfo(1); - }else if (mp3File.hasId3v1Tag()){ - ID3v1 id3v1Tag1 = mp3File.getId3v1Tag(); - title = id3v1Tag1.getTitle(); - artist = id3v1Tag1.getArtist(); - -// track.setRecname(id3v1Tag1.getTitle().replaceAll("\u0000", "")); -// track.setArtist(id3v1Tag1.getArtist().replaceAll("\u0000", "")); -// track.setIsfilledinfo(1); - } - - if(title != null && !title.equals("null") && !title.isEmpty()){ - track.setRecname(title.replaceAll("\u0000", "")); - titleFlag = true; - }else - titleFlag = false; - if(artist != null && !artist.equals("null") && !artist.isEmpty()){ - track.setArtist(artist.replaceAll("\u0000", "")); - artistFlag = true; - }else - artistFlag = false; - - if(artistFlag && titleFlag) - track.setIsfilledinfo(1); - trackRepository.saveAndFlush(track); - } + trackService.setTrackInfo(track.getRecid()); + + if(track.getIscensorial() != null && track.getIscensorial() == 0) + return getNextTrack(deviceId); + if(track.getLength() < 120) + return getNextTrack(deviceId); + trackInfo.put("id", nextTrack.getTrackid().toString()); trackInfo.put("length", String.valueOf(track.getLength())); if(track.getRecname() != null && !track.getRecname().isEmpty() && !track.getRecname().equals("null")) trackInfo.put("name", track.getRecname()); else - trackInfo.put("name", "No name"); + trackInfo.put("name", "Unknown track"); if(track.getArtist() != null && !track.getArtist().isEmpty() && !track.getArtist().equals("null")) trackInfo.put("artist", track.getArtist()); else - trackInfo.put("artist", "NetVox Lab"); + trackInfo.put("artist", "Unknown artist"); trackInfo.put("methodid", nextTrack.getMethodid().toString()); return new ResponseEntity<>(trackInfo, HttpStatus.OK); }catch (Exception ex){ - log.debug("{}", ex.getMessage()); + log.info("{}", ex.getMessage()); return new ResponseEntity<>(HttpStatus.NOT_FOUND); } } else { diff --git a/src/src/main/resources/data/postgresql/schema.sql b/src/src/main/resources/data/postgresql/schema.sql index 136039d..eb1083f 100644 --- a/src/src/main/resources/data/postgresql/schema.sql +++ b/src/src/main/resources/data/postgresql/schema.sql @@ -68,9 +68,9 @@ DECLARE i_ratingid UUID; BEGIN CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; - SELECT uuid_generate_v1() + SELECT uuid_generate_v4() INTO i_historyid; - SELECT uuid_generate_v1() + SELECT uuid_generate_v4() INTO i_ratingid; -- @@ -125,42 +125,14 @@ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION getnexttrackid_v2(i_deviceid uuid) - RETURNS TABLE( - track character varying - , methodid integer) + RETURNS TABLE(track uuid, methodid integer) AS ' DECLARE i_userid uuid = i_deviceid; - rnd integer = (select trunc(random() * 10)); - o_methodid integer; + rnd integer = (select trunc(random() * 10)); -- получаем случайное число от 0 до 9 + o_methodid integer; -- id метода выбора трека BEGIN - -- Добавляем устройство, если его еще не существует - -- Если ID устройства еще нет в БД - IF NOT EXISTS(SELECT recid - FROM devices - WHERE recid = i_deviceid) - THEN - - -- Добавляем нового пользователя - INSERT INTO users (recid, recname, reccreated) SELECT - i_userid, - ''New user recname'', - now(); - - -- Добавляем новое устройство - INSERT INTO devices (recid, userid, recname, reccreated) SELECT - i_deviceid, - i_userid, - ''New device recname'', - now(); - ELSE - SELECT (SELECT userid - FROM devices - WHERE recid = i_deviceid - LIMIT 1) - INTO i_userid; - END IF; -- Выбираем следующий трек @@ -170,31 +142,31 @@ BEGIN THEN o_methodid = 2; RETURN QUERY - SELECT CAST ((trackid) AS CHARACTER VARYING) AS track, o_methodid AS methodid - FROM ratings - WHERE userid = i_userid - AND lastlisten < localtimestamp - interval ''1 day'' - AND ratingsum >= 0 - ORDER BY RANDOM() - LIMIT 1; + SELECT trackid, o_methodid + FROM ratings + WHERE userid = i_userid + AND lastlisten < localtimestamp - interval ''1 day'' + AND ratingsum >= 0 + ORDER BY RANDOM() + LIMIT 1; -- Если такой трек найден - выход из функции, возврат найденного значения IF FOUND - THEN RETURN; + THEN RETURN; END IF; END IF; -- В 1/10 случае выбираем случайный трек из ни разу не прослушанных пользователем треков o_methodid = 3; RETURN QUERY - SELECT CAST ((recid) AS CHARACTER VARYING) AS track, o_methodid AS methodid - FROM tracks - WHERE recid NOT IN + SELECT recid, o_methodid + FROM tracks + WHERE recid NOT IN (SELECT trackid FROM ratings WHERE userid = i_userid) - ORDER BY RANDOM() - LIMIT 1; + ORDER BY RANDOM() + LIMIT 1; -- Если такой трек найден - выход из функции, возврат найденного значения IF FOUND @@ -204,11 +176,124 @@ BEGIN -- Если предыдущие запросы вернули null, выбираем случайный трек o_methodid = 1; RETURN QUERY - SELECT CAST ((recid) AS CHARACTER VARYING) AS track, o_methodid AS methodid - FROM tracks - ORDER BY RANDOM() - LIMIT 1; + SELECT recid, o_methodid + FROM tracks + ORDER BY RANDOM() + LIMIT 1; RETURN; END; ' +LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION getnexttrackid_v3(IN i_deviceid uuid) + RETURNS TABLE(track uuid, methodid integer) AS +' +DECLARE + i_userid uuid = i_deviceid; + rnd integer = (select trunc(random() * 1001)); + o_methodid integer; -- id метода выбора трека + owntracks integer; -- количество "своих" треков пользователя (обрезаем на 900 шт) +BEGIN + -- Выбираем следующий трек + + -- Определяем количество "своих" треков пользователя, ограничивая его 900 + owntracks = (SELECT COUNT(*) FROM ( + SELECT * FROM ratings + WHERE userid = i_userid + AND ratingsum >=0 + LIMIT 900) AS count) ; + + -- Если rnd меньше количества "своих" треков, выбираем трек из треков пользователя (добавленных им или прослушанных до конца) + -- с положительным рейтингом, за исключением прослушанных за последние сутки + + IF (rnd < owntracks) + THEN + o_methodid = 2; -- метод выбора из своих треков + RETURN QUERY + SELECT trackid, o_methodid + FROM ratings + WHERE userid = i_userid + AND lastlisten < localtimestamp - interval ''1 day'' + AND ratingsum >= 0 + ORDER BY RANDOM() + LIMIT 1; + + -- Если такой трек найден - выход из функции, возврат найденного значения + IF FOUND + THEN RETURN; + END IF; + END IF; + + -- В 1/10 случае выбираем случайный трек из ни разу не прослушанных пользователем треков + o_methodid = 3; -- метод выбора из непрослушанных треков + RETURN QUERY + SELECT recid, o_methodid + FROM tracks + WHERE recid NOT IN + (SELECT trackid + FROM ratings + WHERE userid = i_userid) + ORDER BY RANDOM() + LIMIT 1; + + -- Если такой трек найден - выход из функции, возврат найденного значения + IF FOUND + THEN RETURN; + END IF; + + -- Если предыдущие запросы вернули null, выбираем случайный трек + o_methodid = 1; -- метод выбора случайного трека + RETURN QUERY + SELECT recid, o_methodid + FROM tracks + ORDER BY RANDOM() + LIMIT 1; + RETURN; +END; +' +LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION getnexttrack(i_deviceid UUID) + RETURNS TABLE( + track character varying + , methodid integer) + AS +' +DECLARE + i_userid uuid = i_deviceid; -- в дальнейшем заменить получением userid по deviceid +BEGIN + -- Добавляем устройство, если его еще не существует + -- Если ID устройства еще нет в БД + IF NOT EXISTS(SELECT recid + FROM devices + WHERE recid = i_deviceid) + THEN + + -- Добавляем нового пользователя + INSERT INTO users (recid, recname, reccreated) SELECT + i_userid, + ''New user recname'', + now(); + + -- Добавляем новое устройство + INSERT INTO devices (recid, userid, recname, reccreated) SELECT + i_deviceid, + i_userid, + ''New device recname'', + now(); + ELSE + SELECT (SELECT userid + FROM devices + WHERE recid = i_deviceid + LIMIT 1) + INTO i_userid; + END IF; + + -- Возвращаем trackid, конвертируя его в character varying и methodid + RETURN QUERY SELECT + CAST((nexttrack.track) AS CHARACTER VARYING), + nexttrack.methodid + FROM getnexttrackid_v3(i_deviceid) AS nexttrack; +END; +' LANGUAGE plpgsql; \ No newline at end of file From e5094fce3dcfffc17198a45b439397008c76bbcb Mon Sep 17 00:00:00 2001 From: Alexandra Polunina Date: Thu, 8 Dec 2016 17:26:39 +0300 Subject: [PATCH 18/44] =?UTF-8?q?=D0=94=D0=BE=D1=80=D0=B0=D0=B1=D0=BE?= =?UTF-8?q?=D1=82=D0=B0=D0=BB=D0=B0=20=D1=87=D1=82=D0=B5=D0=BD=D0=B8=D0=B5?= =?UTF-8?q?=20=D1=82=D0=B5=D0=B3=D0=BE=D0=B2,=20=D0=B4=D0=BE=D0=B1=D0=B0?= =?UTF-8?q?=D0=B2=D0=B8=D0=BB=D0=B0=20=D1=84=D0=BB=D0=B0=D0=B3=20isexist.?= =?UTF-8?q?=20=D0=9F=D1=80=D0=B8=20=D0=BE=D1=82=D1=81=D1=83=D1=82=D1=81?= =?UTF-8?q?=D1=82=D0=B2=D0=B8=D0=B8=20=D1=84=D0=B0=D0=B9=D0=BB=D0=B0=20?= =?UTF-8?q?=D0=BD=D0=B0=20=D1=81=D0=B5=D1=80=D0=B2=D0=B5=D1=80=D0=B5,=20?= =?UTF-8?q?=D0=B2=D1=8B=D0=B4=D0=B0=D0=B5=D0=BC=20=D0=BF=D0=BE=D0=BB=D1=8C?= =?UTF-8?q?=D0=B7=D0=BE=D0=B2=D0=B0=D1=82=D0=B5=D0=BB=D1=8E=20=D0=B4=D1=80?= =?UTF-8?q?=D1=83=D0=B3=D0=BE=D0=B9=20=D1=82=D1=80=D0=B5=D0=BA,=20=D0=B0?= =?UTF-8?q?=20=D0=B4=D0=B0=D0=BD=D0=BD=D1=8B=D0=B9=20=D0=BF=D0=BE=D0=BC?= =?UTF-8?q?=D0=B5=D1=87=D0=B0=D0=B5=D0=BC=20=D0=BE=D1=82=D1=81=D1=83=D1=82?= =?UTF-8?q?=D1=81=D1=82=D0=B2=D1=83=D1=8E=D1=89=D0=B8=D0=BC.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/src/main/java/ownradio/domain/Track.java | 2 ++ .../service/impl/TrackServiceImpl.java | 32 +++++++++++++------ .../ownradio/web/rest/v3/TrackController.java | 15 ++++++--- .../main/resources/data/postgresql/schema.sql | 10 ++++-- .../repository/HistoryRepositoryTest.java | 2 +- .../repository/TrackRepositoryTest.java | 6 ++-- .../web/rest/v2/TrackControllerTest.java | 2 +- 7 files changed, 48 insertions(+), 21 deletions(-) diff --git a/src/src/main/java/ownradio/domain/Track.java b/src/src/main/java/ownradio/domain/Track.java index e8ad14a..1721b74 100644 --- a/src/src/main/java/ownradio/domain/Track.java +++ b/src/src/main/java/ownradio/domain/Track.java @@ -41,4 +41,6 @@ public class Track extends AbstractEntity { private Integer iscensorial; + private Integer isexist; + } diff --git a/src/src/main/java/ownradio/service/impl/TrackServiceImpl.java b/src/src/main/java/ownradio/service/impl/TrackServiceImpl.java index 5baa869..3304f60 100644 --- a/src/src/main/java/ownradio/service/impl/TrackServiceImpl.java +++ b/src/src/main/java/ownradio/service/impl/TrackServiceImpl.java @@ -44,9 +44,17 @@ public UUID getNextTrackId(UUID deviceId) { public NextTrack getNextTrackIdV2(UUID deviceId) { NextTrack nextTrack = new NextTrack(); List objects = trackRepository.getNextTrackV2(deviceId); - nextTrack.setTrackid(UUID.fromString((String) objects.get(0)[0])); - nextTrack.setMethodid((Integer) objects.get(0)[1]); - return nextTrack; + try{ + if(objects != null) { + nextTrack.setTrackid(UUID.fromString((String) objects.get(0)[0])); + nextTrack.setMethodid((Integer) objects.get(0)[1]); + return nextTrack; + }else{ + return null; + } + }catch (Exception ex){ + return null; + } } @Override @@ -73,22 +81,28 @@ public void setTrackInfo(UUID trackid) { String title = null; boolean artistFlag = false; boolean titleFlag = false; + + byte[] buf; + if (trackid != null) { try { Track track = trackRepository.findOne(trackid); Mp3File mp3File = new Mp3File(track.getPath()); - track.setLength((int) mp3File.getLengthInSeconds());//duration track track.setSize((int) mp3File.getLength() / 1024);//size in kilobytes - if (mp3File.hasId3v2Tag()) { + if (mp3File.hasId3v1Tag()) { + ID3v1 id3v1Tag1 = mp3File.getId3v1Tag(); + title = new String(id3v1Tag1.getTitle().getBytes("UTF16"),"Cp1251").replaceAll("\u0000", "").substring(2); +// title = id3v1Tag1.getTitle(); + artist = new String(id3v1Tag1.getArtist().getBytes("UTF16"),"Cp1251").replaceAll("\u0000", "").substring(2); +// artist = id3v1Tag1.getArtist(); + }else if (mp3File.hasId3v2Tag()) { ID3v2 id3v2Tag2 = mp3File.getId3v2Tag(); title = id3v2Tag2.getTitle(); + title = title.equals(id3v2Tag2.getTitle()) ? title : null; artist = id3v2Tag2.getArtist(); - } else if (mp3File.hasId3v1Tag()) { - ID3v1 id3v1Tag1 = mp3File.getId3v1Tag(); - title = id3v1Tag1.getTitle(); - artist = id3v1Tag1.getArtist(); + artist = artist.equals(id3v2Tag2.getArtist()) ? artist : null; } if (title != null && !title.equals("null") && !title.isEmpty()) { diff --git a/src/src/main/java/ownradio/web/rest/v3/TrackController.java b/src/src/main/java/ownradio/web/rest/v3/TrackController.java index fa9a36c..9fcbe4a 100644 --- a/src/src/main/java/ownradio/web/rest/v3/TrackController.java +++ b/src/src/main/java/ownradio/web/rest/v3/TrackController.java @@ -15,6 +15,7 @@ import ownradio.service.TrackService; import ownradio.util.ResourceUtil; +import java.io.File; import java.util.*; /** @@ -94,13 +95,17 @@ private HttpHeaders getHttpAudioHeaders() { public ResponseEntity getNextTrack(@PathVariable UUID deviceId) { NextTrack nextTrack = trackService.getNextTrackIdV2(deviceId); Map trackInfo = new HashMap<>(); - String artist = null; - String title = null; - boolean artistFlag = false; - boolean titleFlag = false; - if (nextTrack.getTrackid() != null) { + if (nextTrack != null) { try { Track track = trackRepository.findOne(nextTrack.getTrackid()); + + File file = new File(track.getPath()); + if(!file.exists()){ + track.setIsexist(0); + trackRepository.saveAndFlush(track); + return getNextTrack(deviceId); + } + if(track.getIsfilledinfo() == null || track.getIsfilledinfo() != 1) trackService.setTrackInfo(track.getRecid()); diff --git a/src/src/main/resources/data/postgresql/schema.sql b/src/src/main/resources/data/postgresql/schema.sql index eb1083f..6806721 100644 --- a/src/src/main/resources/data/postgresql/schema.sql +++ b/src/src/main/resources/data/postgresql/schema.sql @@ -107,8 +107,8 @@ BEGIN END IF; -- Добавляем трек в базу данных - INSERT INTO tracks (recid, localdevicepathupload, path, deviceid, reccreated) - VALUES (i_trackid, i_localdevicepathupload, i_path, i_deviceid, now()); + INSERT INTO tracks (recid, localdevicepathupload, path, deviceid, reccreated, isexist) + VALUES (i_trackid, i_localdevicepathupload, i_path, i_deviceid, now(), 1); -- Добавляем запись о прослушивании трека в таблицу истории прослушивания INSERT INTO histories (recid, deviceid, trackid, isListen, lastListen, methodid, reccreated) @@ -147,6 +147,7 @@ BEGIN WHERE userid = i_userid AND lastlisten < localtimestamp - interval ''1 day'' AND ratingsum >= 0 + AND (SELECT isexist FROM tracks WHERE recid = trackid) = 1 ORDER BY RANDOM() LIMIT 1; @@ -165,6 +166,7 @@ BEGIN (SELECT trackid FROM ratings WHERE userid = i_userid) + AND isexist = 1 ORDER BY RANDOM() LIMIT 1; @@ -178,6 +180,7 @@ BEGIN RETURN QUERY SELECT recid, o_methodid FROM tracks + WHERE isexist = 1 ORDER BY RANDOM() LIMIT 1; RETURN; @@ -215,6 +218,7 @@ BEGIN WHERE userid = i_userid AND lastlisten < localtimestamp - interval ''1 day'' AND ratingsum >= 0 + AND (SELECT isexist FROM tracks WHERE recid = trackid) = 1 ORDER BY RANDOM() LIMIT 1; @@ -233,6 +237,7 @@ BEGIN (SELECT trackid FROM ratings WHERE userid = i_userid) + AND isexist = 1 ORDER BY RANDOM() LIMIT 1; @@ -246,6 +251,7 @@ BEGIN RETURN QUERY SELECT recid, o_methodid FROM tracks + WHERE isexist = 1 ORDER BY RANDOM() LIMIT 1; RETURN; diff --git a/src/src/test/java/ownradio/repository/HistoryRepositoryTest.java b/src/src/test/java/ownradio/repository/HistoryRepositoryTest.java index aa7e1c6..4880970 100644 --- a/src/src/test/java/ownradio/repository/HistoryRepositoryTest.java +++ b/src/src/test/java/ownradio/repository/HistoryRepositoryTest.java @@ -35,7 +35,7 @@ public class HistoryRepositoryTest { public void setUp() throws Exception { User user = entityManager.persist(new User()); Device device = entityManager.persist(new Device(user, "1")); - Track track = entityManager.persist(new Track("1", device, "1", 0, "", 0, null, null, null)); + Track track = entityManager.persist(new Track("1", device, "1", 0, "", 0, null, null, null, 1)); history = new History(track, Calendar.getInstance(), 0, "post", 1, device); entityManager.persist(history); diff --git a/src/src/test/java/ownradio/repository/TrackRepositoryTest.java b/src/src/test/java/ownradio/repository/TrackRepositoryTest.java index f216043..b4fdc63 100644 --- a/src/src/test/java/ownradio/repository/TrackRepositoryTest.java +++ b/src/src/test/java/ownradio/repository/TrackRepositoryTest.java @@ -37,9 +37,9 @@ public class TrackRepositoryTest { public void setUp() throws Exception { user = entityManager.persist(new User()); device = entityManager.persist(new Device(user, "123")); - entityManager.persist(new Track("1", device, "1", 0, "", 0, null, null, null)); - entityManager.persist(new Track("2", device, "1", 0, "", 0, null, null, null)); - entityManager.persist(new Track("4", device, "1", 0, "", 0, null, null, null)); + entityManager.persist(new Track("1", device, "1", 0, "", 0, null, null, null, 1)); + entityManager.persist(new Track("2", device, "1", 0, "", 0, null, null, null, 1)); + entityManager.persist(new Track("4", device, "1", 0, "", 0, null, null, null, 1)); } @Test diff --git a/src/src/test/java/ownradio/web/rest/v2/TrackControllerTest.java b/src/src/test/java/ownradio/web/rest/v2/TrackControllerTest.java index f5c8bfa..b36bcb6 100644 --- a/src/src/test/java/ownradio/web/rest/v2/TrackControllerTest.java +++ b/src/src/test/java/ownradio/web/rest/v2/TrackControllerTest.java @@ -67,7 +67,7 @@ public void setUp() throws Exception { device.setRecid(DEVICE_UUID); device.setUser(user); - track = new Track(PATH, device, "---", 0, "", 0, null, null, null); + track = new Track(PATH, device, "---", 0, "", 0, null, null, null, 1); String requestParam = "musicFile"; String originalFilename = "test.mp3"; From a76eef4747c0e03d0767cd409cb9ca694d9d3002 Mon Sep 17 00:00:00 2001 From: Alexandra Polunina Date: Fri, 16 Dec 2016 09:36:36 +0300 Subject: [PATCH 19/44] =?UTF-8?q?=D0=92=D1=8B=D0=BD=D0=B5=D1=81=D0=BB?= =?UTF-8?q?=D0=B0=20=D0=B2=20=D0=BE=D1=82=D0=B4=D0=B5=D0=BB=D1=8C=D0=BD?= =?UTF-8?q?=D1=83=D1=8E=20=D0=BF=D0=B0=D0=BF=D0=BA=D1=83=20api=20v3,=20?= =?UTF-8?q?=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8=D0=BB=D0=B0=20=D1=81=D0=BA?= =?UTF-8?q?=D1=80=D0=B8=D0=BF=D1=82=D1=8B=20=D0=91=D0=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dbScripts/ownradio_db_v3.sql | 584 ++++++++++++++++++ src/README.md | 62 +- src/pom.xml | 6 - .../main/java/ownradio/command/InitDb.java | 3 - .../java/ownradio/domain/AbstractEntity.java | 12 +- .../main/java/ownradio/domain/History.java | 10 +- src/src/main/java/ownradio/domain/Rating.java | 6 +- src/src/main/java/ownradio/domain/Track.java | 14 - .../ownradio/repository/TrackRepository.java | 5 - .../java/ownradio/service/TrackService.java | 7 - .../service/impl/TrackServiceImpl.java | 73 --- .../web/rest/v2/HistoryController.java | 17 +- .../ownradio/web/rest/v2/TrackController.java | 7 +- .../main/resources/application-dev.properties | 2 +- .../resources/application-prod.properties | 11 +- .../main/resources/data/postgresql/schema.sql | 193 +----- .../repository/HistoryRepositoryTest.java | 13 +- .../repository/TrackRepositoryTest.java | 6 +- .../web/rest/v2/HistoryControllerTest.java | 32 +- .../web/rest/v2/TrackControllerTest.java | 31 +- v3/.gitignore | 6 + v3/.mvn/wrapper/maven-wrapper.jar | Bin 0 -> 49502 bytes v3/.mvn/wrapper/maven-wrapper.properties | 1 + v3/README.md | 113 ++++ v3/msg/masseage_en.properties | 1 + v3/msg/masseage_ru.properties | 1 + v3/mvnw | 233 +++++++ v3/mvnw.cmd | 145 +++++ v3/pom.xml | 80 +++ v3/src/main/java/ownradio/Application.java | 25 + .../java/ownradio/annotation/DisplayName.java | 24 + v3/src/main/java/ownradio/command/InitDb.java | 31 + .../java/ownradio/domain/AbstractEntity.java | 65 ++ v3/src/main/java/ownradio/domain/Device.java | 34 + .../java/ownradio/domain/DownloadTrack.java | 30 + v3/src/main/java/ownradio/domain/History.java | 47 ++ .../main/java/ownradio/domain/NextTrack.java | 0 v3/src/main/java/ownradio/domain/Rating.java | 41 ++ v3/src/main/java/ownradio/domain/Track.java | 46 ++ v3/src/main/java/ownradio/domain/User.java | 24 + .../ownradio/repository/DeviceRepository.java | 14 + .../repository/DownloadTrackRepository.java | 14 + .../repository/HistoryRepository.java | 14 + .../ownradio/repository/RatingRepository.java | 22 + .../ownradio/repository/TrackRepository.java | 25 + .../ownradio/repository/UserRepository.java | 14 + .../java/ownradio/service/DeviceService.java | 16 + .../service/DownloadTrackService.java | 12 + .../java/ownradio/service/HistoryService.java | 12 + .../java/ownradio/service/RatingService.java | 12 + .../java/ownradio/service/TrackService.java | 27 + .../java/ownradio/service/UserService.java | 16 + .../service/impl/DeviceServiceImpl.java | 31 + .../impl/DownloadTrackServiceImpl.java | 24 + .../service/impl/HistoryServiceImpl.java | 45 ++ .../service/impl/RatingServiceImpl.java | 24 + .../service/impl/TrackServiceImpl.java | 127 ++++ .../service/impl/UserServiceImpl.java | 26 + .../main/java/ownradio/util/ReflectUtil.java | 51 ++ .../main/java/ownradio/util/ResourceUtil.java | 69 +++ .../web/rest/v2/HistoryController.java | 69 +++ .../ownradio/web/rest/v2/TrackController.java | 102 +++ .../web/rest/v3/HistoryController.java | 0 .../ownradio/web/rest/v3/TrackController.java | 0 .../main/resources/application-dev.properties | 17 + .../resources/application-prod.properties | 25 + v3/src/main/resources/data/h2/schema.sql | 0 .../main/resources/data/postgresql/schema.sql | 305 +++++++++ .../test/java/ownradio/ApplicationTests.java | 18 + .../test/java/ownradio/domain/UserTest.java | 44 ++ .../repository/HistoryRepositoryTest.java | 68 ++ .../repository/RatingRepositoryTest.java | 45 ++ .../repository/TrackRepositoryTest.java | 57 ++ .../ownradio/service/TrackServiceTest.java | 76 +++ .../web/rest/v2/HistoryControllerTest.java | 118 ++++ .../web/rest/v2/TrackControllerTest.java | 185 ++++++ 76 files changed, 3316 insertions(+), 449 deletions(-) create mode 100644 dbScripts/ownradio_db_v3.sql create mode 100644 v3/.gitignore create mode 100644 v3/.mvn/wrapper/maven-wrapper.jar create mode 100644 v3/.mvn/wrapper/maven-wrapper.properties create mode 100644 v3/README.md create mode 100644 v3/msg/masseage_en.properties create mode 100644 v3/msg/masseage_ru.properties create mode 100644 v3/mvnw create mode 100644 v3/mvnw.cmd create mode 100644 v3/pom.xml create mode 100644 v3/src/main/java/ownradio/Application.java create mode 100644 v3/src/main/java/ownradio/annotation/DisplayName.java create mode 100644 v3/src/main/java/ownradio/command/InitDb.java create mode 100644 v3/src/main/java/ownradio/domain/AbstractEntity.java create mode 100644 v3/src/main/java/ownradio/domain/Device.java create mode 100644 v3/src/main/java/ownradio/domain/DownloadTrack.java create mode 100644 v3/src/main/java/ownradio/domain/History.java rename {src => v3}/src/main/java/ownradio/domain/NextTrack.java (100%) create mode 100644 v3/src/main/java/ownradio/domain/Rating.java create mode 100644 v3/src/main/java/ownradio/domain/Track.java create mode 100644 v3/src/main/java/ownradio/domain/User.java create mode 100644 v3/src/main/java/ownradio/repository/DeviceRepository.java create mode 100644 v3/src/main/java/ownradio/repository/DownloadTrackRepository.java create mode 100644 v3/src/main/java/ownradio/repository/HistoryRepository.java create mode 100644 v3/src/main/java/ownradio/repository/RatingRepository.java create mode 100644 v3/src/main/java/ownradio/repository/TrackRepository.java create mode 100644 v3/src/main/java/ownradio/repository/UserRepository.java create mode 100644 v3/src/main/java/ownradio/service/DeviceService.java create mode 100644 v3/src/main/java/ownradio/service/DownloadTrackService.java create mode 100644 v3/src/main/java/ownradio/service/HistoryService.java create mode 100644 v3/src/main/java/ownradio/service/RatingService.java create mode 100644 v3/src/main/java/ownradio/service/TrackService.java create mode 100644 v3/src/main/java/ownradio/service/UserService.java create mode 100644 v3/src/main/java/ownradio/service/impl/DeviceServiceImpl.java create mode 100644 v3/src/main/java/ownradio/service/impl/DownloadTrackServiceImpl.java create mode 100644 v3/src/main/java/ownradio/service/impl/HistoryServiceImpl.java create mode 100644 v3/src/main/java/ownradio/service/impl/RatingServiceImpl.java create mode 100644 v3/src/main/java/ownradio/service/impl/TrackServiceImpl.java create mode 100644 v3/src/main/java/ownradio/service/impl/UserServiceImpl.java create mode 100644 v3/src/main/java/ownradio/util/ReflectUtil.java create mode 100644 v3/src/main/java/ownradio/util/ResourceUtil.java create mode 100644 v3/src/main/java/ownradio/web/rest/v2/HistoryController.java create mode 100644 v3/src/main/java/ownradio/web/rest/v2/TrackController.java rename {src => v3}/src/main/java/ownradio/web/rest/v3/HistoryController.java (100%) rename {src => v3}/src/main/java/ownradio/web/rest/v3/TrackController.java (100%) create mode 100644 v3/src/main/resources/application-dev.properties create mode 100644 v3/src/main/resources/application-prod.properties create mode 100644 v3/src/main/resources/data/h2/schema.sql create mode 100644 v3/src/main/resources/data/postgresql/schema.sql create mode 100644 v3/src/test/java/ownradio/ApplicationTests.java create mode 100644 v3/src/test/java/ownradio/domain/UserTest.java create mode 100644 v3/src/test/java/ownradio/repository/HistoryRepositoryTest.java create mode 100644 v3/src/test/java/ownradio/repository/RatingRepositoryTest.java create mode 100644 v3/src/test/java/ownradio/repository/TrackRepositoryTest.java create mode 100644 v3/src/test/java/ownradio/service/TrackServiceTest.java create mode 100644 v3/src/test/java/ownradio/web/rest/v2/HistoryControllerTest.java create mode 100644 v3/src/test/java/ownradio/web/rest/v2/TrackControllerTest.java diff --git a/dbScripts/ownradio_db_v3.sql b/dbScripts/ownradio_db_v3.sql new file mode 100644 index 0000000..0c7e6bc --- /dev/null +++ b/dbScripts/ownradio_db_v3.sql @@ -0,0 +1,584 @@ +-- Database: "ownRadioJava" + +-- DROP DATABASE "ownRadioJava"; + +CREATE DATABASE "ownRadioJava" + WITH OWNER = postgres + ENCODING = 'UTF8' + TABLESPACE = pg_default + LC_COLLATE = 'ru_RU.UTF-8' + LC_CTYPE = 'ru_RU.UTF-8' + CONNECTION LIMIT = -1; + + +-- Table: public.devices + +-- DROP TABLE public.devices; + +CREATE TABLE public.devices +( + recid uuid NOT NULL, + reccreated timestamp without time zone, + recname character varying(255), + recupdated timestamp without time zone, + userid uuid, + CONSTRAINT devices_pkey PRIMARY KEY (recid), + CONSTRAINT fk9xjj6x9ueb7id644i4ycukpug FOREIGN KEY (userid) + REFERENCES public.users (recid) MATCH SIMPLE + ON UPDATE NO ACTION ON DELETE NO ACTION, + CONSTRAINT fkrfbri1ymrwywdydc4dgywe1bt FOREIGN KEY (userid) + REFERENCES public.users (recid) MATCH SIMPLE + ON UPDATE NO ACTION ON DELETE NO ACTION +) +WITH ( + OIDS=FALSE, + autovacuum_enabled=true +); +ALTER TABLE public.devices + OWNER TO postgres; + + +-- Table: public.downloadtracks + +-- DROP TABLE public.downloadtracks; + +CREATE TABLE public.downloadtracks +( + recid uuid NOT NULL, + reccreated timestamp without time zone, + recname character varying(255), + recupdated timestamp without time zone, + deviceid uuid, + trackid uuid, + CONSTRAINT download_tracks_pkey PRIMARY KEY (recid), + CONSTRAINT fkcsqwol33buwhcijea4w2ty5k2 FOREIGN KEY (trackid) + REFERENCES public.tracks (recid) MATCH SIMPLE + ON UPDATE NO ACTION ON DELETE NO ACTION, + CONSTRAINT fkerttthmbxldbrlonworltybaq FOREIGN KEY (deviceid) + REFERENCES public.devices (recid) MATCH SIMPLE + ON UPDATE NO ACTION ON DELETE NO ACTION, + CONSTRAINT fkf38s60by3ys41nkrvo0wpghqu FOREIGN KEY (deviceid) + REFERENCES public.devices (recid) MATCH SIMPLE + ON UPDATE NO ACTION ON DELETE NO ACTION, + CONSTRAINT fklkvcfwa2nxrdfs6q20x3slfsk FOREIGN KEY (trackid) + REFERENCES public.tracks (recid) MATCH SIMPLE + ON UPDATE NO ACTION ON DELETE NO ACTION +) +WITH ( + OIDS=FALSE, + autovacuum_enabled=true +); +ALTER TABLE public.downloadtracks + OWNER TO postgres; + + + +-- Table: public.histories + +-- DROP TABLE public.histories; + +CREATE TABLE public.histories +( + recid uuid NOT NULL, + reccreated timestamp without time zone, + recname character varying(255), + recupdated timestamp without time zone, + islisten integer NOT NULL, + lastlisten timestamp without time zone NOT NULL, + method character varying(255), + deviceid uuid, + trackid uuid, + userid uuid, + methodid integer, + CONSTRAINT histories_pkey PRIMARY KEY (recid), + CONSTRAINT fk66xoney4xhu7rp7yxwye0tuw4 FOREIGN KEY (deviceid) + REFERENCES public.devices (recid) MATCH SIMPLE + ON UPDATE NO ACTION ON DELETE NO ACTION, + CONSTRAINT fk6kk9amb55jghcg30cxstw4yw FOREIGN KEY (trackid) + REFERENCES public.tracks (recid) MATCH SIMPLE + ON UPDATE NO ACTION ON DELETE NO ACTION, + CONSTRAINT fk8w9eva74w7t7xtf2opb33f8bq FOREIGN KEY (userid) + REFERENCES public.users (recid) MATCH SIMPLE + ON UPDATE NO ACTION ON DELETE NO ACTION, + CONSTRAINT fkbc0htpqvevq196g2vpa9ipkci FOREIGN KEY (trackid) + REFERENCES public.tracks (recid) MATCH SIMPLE + ON UPDATE NO ACTION ON DELETE NO ACTION, + CONSTRAINT fkbjn2i4ry8qwwp12wwbq4n96aa FOREIGN KEY (deviceid) + REFERENCES public.devices (recid) MATCH SIMPLE + ON UPDATE NO ACTION ON DELETE NO ACTION +) +WITH ( + OIDS=FALSE, + autovacuum_enabled=true +); +ALTER TABLE public.histories + OWNER TO postgres; + + +-- Table: public.ratings + +-- DROP TABLE public.ratings; + +CREATE TABLE public.ratings +( + recid uuid NOT NULL, + reccreated timestamp without time zone, + recname character varying(255), + recupdated timestamp without time zone, + lastlisten timestamp without time zone NOT NULL, + ratingsum integer NOT NULL, + trackid uuid, + userid uuid, + CONSTRAINT ratings_pkey PRIMARY KEY (recid), + CONSTRAINT fk1wogw2je0eguqyvbegwgqwmku FOREIGN KEY (trackid) + REFERENCES public.tracks (recid) MATCH SIMPLE + ON UPDATE NO ACTION ON DELETE NO ACTION, + CONSTRAINT fk9obht0874ty4owpd9a3hqa7gr FOREIGN KEY (userid) + REFERENCES public.users (recid) MATCH SIMPLE + ON UPDATE NO ACTION ON DELETE NO ACTION, + CONSTRAINT fkb3354ee2xxvdrbyq9f42jdayd FOREIGN KEY (userid) + REFERENCES public.users (recid) MATCH SIMPLE + ON UPDATE NO ACTION ON DELETE NO ACTION, + CONSTRAINT fkkewx1qhpt2egcdq7x92cv63p7 FOREIGN KEY (trackid) + REFERENCES public.tracks (recid) MATCH SIMPLE + ON UPDATE NO ACTION ON DELETE NO ACTION +) +WITH ( + OIDS=FALSE, + autovacuum_enabled=true +); +ALTER TABLE public.ratings + OWNER TO postgres; + +-- Index: public.idx_lastlisten + +-- DROP INDEX public.idx_lastlisten; + +CREATE INDEX idx_lastlisten + ON public.ratings + USING btree + (lastlisten); + +-- Index: public.idx_trackid + +-- DROP INDEX public.idx_trackid; + +CREATE INDEX idx_trackid + ON public.ratings + USING btree + (trackid); + +-- Index: public.idx_userid + +-- DROP INDEX public.idx_userid; + +CREATE INDEX idx_userid + ON public.ratings + USING btree + (userid); + + +-- Table: public.tracks + +-- DROP TABLE public.tracks; + +CREATE TABLE public.tracks +( + recid uuid NOT NULL, + reccreated timestamp without time zone, + recname character varying(255), + recupdated timestamp without time zone, + localdevicepathupload character varying(255) NOT NULL, + path character varying(255), + deviceid uuid, + uploaduserid uuid, + artist character varying(255), + iscensorial integer, + iscorrect integer, + isfilledinfo integer, + length integer, + size integer, + CONSTRAINT tracks_pkey PRIMARY KEY (recid), + CONSTRAINT fk4n44h9fs1to11otqj5ek7xtus FOREIGN KEY (deviceid) + REFERENCES public.devices (recid) MATCH SIMPLE + ON UPDATE NO ACTION ON DELETE NO ACTION, + CONSTRAINT fk7901v2785f03qrr9ruiwy7nd FOREIGN KEY (deviceid) + REFERENCES public.devices (recid) MATCH SIMPLE + ON UPDATE NO ACTION ON DELETE NO ACTION, + CONSTRAINT fkfp7ki0smfcrvbvfjdnddxi1fb FOREIGN KEY (uploaduserid) + REFERENCES public.users (recid) MATCH SIMPLE + ON UPDATE NO ACTION ON DELETE NO ACTION +) +WITH ( + OIDS=FALSE, + autovacuum_enabled=true +); +ALTER TABLE public.tracks + OWNER TO postgres; + + +-- Table: public.users + +-- DROP TABLE public.users; + +CREATE TABLE public.users +( + recid uuid NOT NULL, + reccreated timestamp without time zone, + recname character varying(255), + recupdated timestamp without time zone, + CONSTRAINT users_pkey PRIMARY KEY (recid) +) +WITH ( + OIDS=FALSE, + autovacuum_enabled=true +); +ALTER TABLE public.users + OWNER TO postgres; + + +-- Function: public.getnexttrack(uuid) + +-- DROP FUNCTION public.getnexttrack(uuid); + +CREATE OR REPLACE FUNCTION public.getnexttrack(IN i_deviceid uuid) + RETURNS TABLE(track character varying, methodid integer) AS +$BODY$ +DECLARE + i_userid uuid = i_deviceid; -- в дальнейшем заменить получением userid по deviceid +BEGIN + -- Добавляем устройство, если его еще не существует + -- Если ID устройства еще нет в БД + IF NOT EXISTS(SELECT recid + FROM devices + WHERE recid = i_deviceid) + THEN + + -- Добавляем нового пользователя + INSERT INTO users (recid, recname, reccreated) SELECT + i_userid, + 'New user recname', + now(); + + -- Добавляем новое устройство + INSERT INTO devices (recid, userid, recname, reccreated) SELECT + i_deviceid, + i_userid, + 'New device recname', + now(); + ELSE + SELECT (SELECT userid + FROM devices + WHERE recid = i_deviceid + LIMIT 1) + INTO i_userid; + END IF; + + -- Возвращаем trackid, конвертируя его в character varying и methodid + RETURN QUERY SELECT + CAST((nexttrack.track) AS CHARACTER VARYING), + nexttrack.methodid + FROM getnexttrackid_v3(i_deviceid) AS nexttrack; +END; +$BODY$ + LANGUAGE plpgsql VOLATILE + COST 100 + ROWS 1000; +ALTER FUNCTION public.getnexttrack(uuid) + OWNER TO "postgres"; + + +-- Function: public.getnexttrackid(uuid) + +-- DROP FUNCTION public.getnexttrackid(uuid); + +CREATE OR REPLACE FUNCTION public.getnexttrackid(i_deviceid uuid) + RETURNS SETOF uuid AS +$BODY$ +DECLARE + i_userid uuid = i_deviceid; + BEGIN + -- Добавляем устройство, если его еще не существует + -- Если ID устройства еще нет в БД + IF NOT EXISTS(SELECT recid + FROM devices + WHERE recid = i_deviceid) + THEN + + -- Добавляем нового пользователя + INSERT INTO users (recid, recname, reccreated) SELECT + i_userid, + 'New user recname', + now(); + + -- Добавляем новое устройство + INSERT INTO devices (recid, userid, recname, reccreated) SELECT + i_deviceid, + i_userid, + 'New device recname', + now(); + ELSE + SELECT (SELECT userid + FROM devices + WHERE recid = i_deviceid + LIMIT 1) + INTO i_userid; + END IF; + + RETURN QUERY + SELECT tracks.recid + FROM tracks + LEFT JOIN + ratings + ON tracks.recid = ratings.trackid AND ratings.userid = i_userid + WHERE ratings.ratingsum >=0 OR ratings.ratingsum is null + ORDER BY RANDOM() + LIMIT 1; +END; +$BODY$ + LANGUAGE plpgsql VOLATILE + COST 100 + ROWS 1000; +ALTER FUNCTION public.getnexttrackid(uuid) + OWNER TO postgres; + + + -- Function: public.getnexttrackid_string(uuid) + +-- DROP FUNCTION public.getnexttrackid_string(uuid); + +CREATE OR REPLACE FUNCTION public.getnexttrackid_string(i_deviceid uuid) + RETURNS SETOF character varying AS +$BODY$ +BEGIN + RETURN QUERY SELECT CAST(getnexttrackid(i_deviceid) AS CHARACTER VARYING); +END; +$BODY$ + LANGUAGE plpgsql VOLATILE + COST 100 + ROWS 1000; +ALTER FUNCTION public.getnexttrackid_string(uuid) + OWNER TO postgres; + + +-- Function: public.getnexttrackid_v2(uuid) + +-- DROP FUNCTION public.getnexttrackid_v2(uuid); + +CREATE OR REPLACE FUNCTION public.getnexttrackid_v2(IN i_deviceid uuid) + RETURNS TABLE(track uuid, methodid integer) AS +$BODY$ +DECLARE + i_userid uuid = i_deviceid; + rnd integer = (select trunc(random() * 10)); -- получаем случайное число от 0 до 9 + o_methodid integer; -- id метода выбора трека +BEGIN + + -- Выбираем следующий трек + + -- В 9/10 случаях выбираем трек из треков пользователя (добавленных им или прослушанных до конца) + -- с положительным рейтингом, за исключением прослушанных за последние сутки + IF (rnd > 1) + THEN + o_methodid = 2; + RETURN QUERY + SELECT trackid, o_methodid + FROM ratings + WHERE userid = i_userid + AND lastlisten < localtimestamp - interval '1 day' + AND ratingsum >= 0 + ORDER BY RANDOM() + LIMIT 1; + + -- Если такой трек найден - выход из функции, возврат найденного значения + IF FOUND + THEN RETURN; + END IF; + END IF; + + -- В 1/10 случае выбираем случайный трек из ни разу не прослушанных пользователем треков + o_methodid = 3; + RETURN QUERY + SELECT recid, o_methodid + FROM tracks + WHERE recid NOT IN + (SELECT trackid + FROM ratings + WHERE userid = i_userid) + ORDER BY RANDOM() + LIMIT 1; + + -- Если такой трек найден - выход из функции, возврат найденного значения + IF FOUND + THEN RETURN; + END IF; + + -- Если предыдущие запросы вернули null, выбираем случайный трек + o_methodid = 1; + RETURN QUERY + SELECT recid, o_methodid + FROM tracks + ORDER BY RANDOM() + LIMIT 1; + RETURN; +END; +$BODY$ + LANGUAGE plpgsql VOLATILE + COST 100 + ROWS 1000; +ALTER FUNCTION public.getnexttrackid_v2(uuid) + OWNER TO postgres; + + +-- Function: public.getnexttrackid_v3(uuid) + +-- DROP FUNCTION public.getnexttrackid_v3(uuid); + +CREATE OR REPLACE FUNCTION public.getnexttrackid_v3(IN i_deviceid uuid) + RETURNS TABLE(track uuid, methodid integer) AS +$BODY$ +DECLARE + i_userid uuid = i_deviceid; + rnd integer = (select trunc(random() * 1001)); + o_methodid integer; -- id метода выбора трека + owntracks integer; -- количество "своих" треков пользователя (обрезаем на 900 шт) +BEGIN + -- Выбираем следующий трек + + -- Определяем количество "своих" треков пользователя, ограничивая его 900 + owntracks = (SELECT COUNT(*) FROM ( + SELECT * FROM ratings + WHERE userid = i_userid + AND ratingsum >=0 + LIMIT 900) AS count) ; + + -- Если rnd меньше количества "своих" треков, выбираем трек из треков пользователя (добавленных им или прослушанных до конца) + -- с положительным рейтингом, за исключением прослушанных за последние сутки + + IF (rnd < owntracks) + THEN + o_methodid = 2; -- метод выбора из своих треков + RETURN QUERY + SELECT trackid, o_methodid + FROM ratings + WHERE userid = i_userid + AND lastlisten < localtimestamp - interval '1 day' + AND ratingsum >= 0 + ORDER BY RANDOM() + LIMIT 1; + + -- Если такой трек найден - выход из функции, возврат найденного значения + IF FOUND + THEN RETURN; + END IF; + END IF; + + -- В 1/10 случае выбираем случайный трек из ни разу не прослушанных пользователем треков + o_methodid = 3; -- метод выбора из непрослушанных треков + RETURN QUERY + SELECT recid, o_methodid + FROM tracks + WHERE recid NOT IN + (SELECT trackid + FROM ratings + WHERE userid = i_userid) + ORDER BY RANDOM() + LIMIT 1; + + -- Если такой трек найден - выход из функции, возврат найденного значения + IF FOUND + THEN RETURN; + END IF; + + -- Если предыдущие запросы вернули null, выбираем случайный трек + o_methodid = 1; -- метод выбора случайного трека + RETURN QUERY + SELECT recid, o_methodid + FROM tracks + ORDER BY RANDOM() + LIMIT 1; + RETURN; +END; +$BODY$ + LANGUAGE plpgsql VOLATILE + COST 100 + ROWS 1000; +ALTER FUNCTION public.getnexttrackid_v3(uuid) + OWNER TO postgres; + + +-- Function: public.registertrack(uuid, character varying, character varying, uuid) + +-- DROP FUNCTION public.registertrack(uuid, character varying, character varying, uuid); + +CREATE OR REPLACE FUNCTION public.registertrack( + i_trackid uuid, + i_localdevicepathupload character varying, + i_path character varying, + i_deviceid uuid) + RETURNS boolean AS +$BODY$ +DECLARE + i_userid UUID = i_deviceid; + i_historyid UUID; + i_ratingid UUID; +BEGIN + CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; + SELECT uuid_generate_v4() + INTO i_historyid; + SELECT uuid_generate_v4() + INTO i_ratingid; + + -- + -- Функция добавляет запись о треке в таблицу треков и делает сопутствующие записи в + -- таблицу статистики прослушивания и рейтингов. Если пользователя, загружающего трек + -- нет в базе, то он добавляется в таблицу пользователей. + -- + + -- Добавляем устройство, если его еще не существует + -- Если ID устройства еще нет в БД + IF NOT EXISTS(SELECT recid + FROM devices + WHERE recid = i_deviceid) + THEN + + -- Добавляем нового пользователя + INSERT INTO users (recid, recname, reccreated) SELECT + i_userid, + 'New user recname', + now(); + + -- Добавляем новое устройство + INSERT INTO devices (recid, userid, recname, reccreated) SELECT + i_deviceid, + i_userid, + 'New device recname', + now(); + ELSE + SELECT (SELECT userid + FROM devices + WHERE recid = i_deviceid + LIMIT 1) + INTO i_userid; + END IF; + + -- Добавляем трек в базу данных + INSERT INTO tracks (recid, localdevicepathupload, path, deviceid, reccreated) + VALUES (i_trackid, i_localdevicepathupload, i_path, i_deviceid, now()); + + -- Добавляем запись о прослушивании трека в таблицу истории прослушивания + INSERT INTO histories (recid, deviceid, trackid, isListen, lastListen, methodid, reccreated) + VALUES (i_historyid, i_deviceid, i_trackid, 1, now(), 2, now()); + + -- Добавляем запись в таблицу рейтингов + INSERT INTO ratings (recid, userid, trackid, lastListen, ratingsum, reccreated) + VALUES (i_ratingid, i_userid, i_trackid, now(), 1, now()); + + RETURN TRUE; +END; +$BODY$ + LANGUAGE plpgsql VOLATILE + COST 100; +ALTER FUNCTION public.registertrack(uuid, character varying, character varying, uuid) + OWNER TO postgres; + diff --git a/src/README.md b/src/README.md index d663e2b..4f3a4cd 100644 --- a/src/README.md +++ b/src/README.md @@ -18,7 +18,7 @@ Web API ### Загрузка трека на сервер -##### POST /v2/tracks +##### POST /api/v2/tracks * `fileGuid` – UUID трека * `fileName` – имя файла * `filePath` - Локальный путь к файлу на пользовательском устройстве @@ -31,7 +31,7 @@ Web API * `500, "Internal Server Error"` – если произошел сбой на сервере ### Получение трека с сервера -##### GET /v2/tracks/{trackId} +##### GET /api/v2/tracks/{trackId} * `{trackId}` – UUID трека ##### HttpStatus @@ -39,7 +39,7 @@ Web API * `404, "Not Found"` – если трек с таким recid не найден ### Получение следующего трека с сервера -##### GET /v2/tracks/{deviceId}/next +##### GET /api/v2/tracks/{deviceId}/next * `{deviceId}` – UUID девайса ##### HttpStatus @@ -47,7 +47,7 @@ Web API * `404, "Not Found"` – если трек с таким recid не найден ### Сохранение истории треков -##### POST /v2/histories/{deviceId}/{trackId} +##### POST /api/v2/histories/{deviceId}/{trackId} * `{trackId}` – UUID прослушанного трека * `{deviceId}` – UUID устройства где был прослушан трек * `lastListen` - Время последнего прослушивания или пропуска трека для данного пользователя @@ -57,57 +57,3 @@ Web API ##### HttpStatus * `200, "OK"` – если все ок * `500, "Internal Server Error"` – если произошел сбой на сервере - -Web API v3 ---- - -### Загрузка трека на сервер - -##### POST /v3/tracks -* `fileGuid` – UUID трека -* `filePath` - Полный локальный путь к файлу на пользовательском устройстве, включая имя файла (String) -* `deviceId` – UUID device -* `musicFile` – прикрепленный файл - -##### HttpStatus -* `400, "Bad Request"` - Если пользователь ввел некорректные данные -* `201, "Created"` – если все ок -* `500, "Internal Server Error"` – если произошел сбой на сервере - -### Получение трека с сервера -##### GET /v3/tracks/{trackId} -* `{trackId}` – UUID трека - -##### HttpStatus -* `200, "OK"` – в теле ответа будет лежать трек -* `404, "Not Found"` – если трек с таким recid не найден - -### Получение следующего трека с сервера -##### GET /v3/tracks/{deviceId}/next -* `{deviceId}` – UUID девайса - -##### Response -Content-Type →application/json;charset=UTF-8 -{ - "artist": "Artist", - "length": "duration", - "name": "Title", - "methodid": "1", - "id": "00000000-0000-0000-0000-000000000000" -} - -##### HttpStatus -* `200, "OK"` – в теле ответа будет лежать UUID трека -* `404, "Not Found"` – если трек с таким recid не найден - -### Сохранение истории треков -##### POST /v3/histories/{deviceId}/{trackId} -* `{trackId}` – UUID прослушанного трека -* `{deviceId}` – UUID устройства где был прослушан трек -* `lastListen` - Время последнего прослушивания или пропуска трека для данного пользователя ("yyyy-MM-ddTHH:mm:ss") -* `isListen` - Признак прослушан ли трек до конца: 1 - прослушан, -1 – нет (int) -* `methodid` - ID метода выбора трека (int), равен значению, полученному при получении данных для следующего трека - -##### HttpStatus -* `200, "OK"` – если все ок -* `500, "Internal Server Error"` – если произошел сбой на сервере diff --git a/src/pom.xml b/src/pom.xml index 2c81444..7e14138 100644 --- a/src/pom.xml +++ b/src/pom.xml @@ -58,12 +58,6 @@ spring-boot-starter-test test - - - com.mpatric - mp3agic - 0.8.3 - diff --git a/src/src/main/java/ownradio/command/InitDb.java b/src/src/main/java/ownradio/command/InitDb.java index ab61a8b..4568789 100644 --- a/src/src/main/java/ownradio/command/InitDb.java +++ b/src/src/main/java/ownradio/command/InitDb.java @@ -8,8 +8,6 @@ import ownradio.domain.User; import ownradio.service.UserService; -import java.util.TimeZone; - /** * Класс инициализирует БД первоначальными данными * @@ -24,7 +22,6 @@ public class InitDb implements CommandLineRunner { @Override public void run(String... strings) throws Exception { - TimeZone.setDefault(TimeZone.getTimeZone("Etc/UTC")); User user = userService.save(new User()); log.debug("User recid: {}", user.getRecid()); } diff --git a/src/src/main/java/ownradio/domain/AbstractEntity.java b/src/src/main/java/ownradio/domain/AbstractEntity.java index 991ae20..d740f08 100644 --- a/src/src/main/java/ownradio/domain/AbstractEntity.java +++ b/src/src/main/java/ownradio/domain/AbstractEntity.java @@ -7,7 +7,6 @@ import javax.persistence.*; import java.io.Serializable; -import java.util.Calendar; import java.util.Date; import java.util.UUID; @@ -31,20 +30,17 @@ public abstract class AbstractEntity implements Serializable { private String recname; - @Temporal(TemporalType.TIMESTAMP) - private Calendar reccreated; - - @Temporal(TemporalType.TIMESTAMP) - private Calendar recupdated; + private Date reccreated; + private Date recupdated; @PrePersist public void beforePersist() { - setReccreated(Calendar.getInstance()); + setReccreated(new Date()); } @PreUpdate public void beforeUpdate() { - setRecupdated(Calendar.getInstance()); + setRecupdated(new Date()); } @Override diff --git a/src/src/main/java/ownradio/domain/History.java b/src/src/main/java/ownradio/domain/History.java index 679afa3..c41c696 100644 --- a/src/src/main/java/ownradio/domain/History.java +++ b/src/src/main/java/ownradio/domain/History.java @@ -8,7 +8,6 @@ import org.springframework.format.annotation.DateTimeFormat; import javax.persistence.*; -import java.util.Calendar; import java.util.Date; /** @@ -28,19 +27,16 @@ public class History extends AbstractEntity { @JoinColumn(name = "trackid") private Track track; - @DateTimeFormat(pattern = "dd-MM-yyyy'T'H:m:s") - @Temporal(TemporalType.TIMESTAMP) + @DateTimeFormat(pattern = "dd/MM/yyyy") @Column(name = "lastlisten", nullable = false) - private Calendar lastListen; + private Date lastListen; @Column(name = "islisten", nullable = false) private int isListen; // 1, -1 -// @Column(nullable = false) + @Column(nullable = false) private String method; - private Integer methodid; - @ManyToOne @JoinColumn(name = "deviceid") private Device device; diff --git a/src/src/main/java/ownradio/domain/Rating.java b/src/src/main/java/ownradio/domain/Rating.java index 02e753f..78ab1b9 100644 --- a/src/src/main/java/ownradio/domain/Rating.java +++ b/src/src/main/java/ownradio/domain/Rating.java @@ -8,7 +8,6 @@ import org.springframework.format.annotation.DateTimeFormat; import javax.persistence.*; -import java.util.Calendar; import java.util.Date; /** @@ -31,10 +30,9 @@ public class Rating extends AbstractEntity { @JoinColumn(name = "trackid") private Track track; - @DateTimeFormat(pattern = "dd-MM-yyyy'T'H:m:s") + @DateTimeFormat(pattern = "dd/MM/yyyy") @Column(nullable = false) - @Temporal(TemporalType.TIMESTAMP) - private Calendar lastlisten; + private Date lastlisten; @Column(nullable = false) private Integer ratingsum; diff --git a/src/src/main/java/ownradio/domain/Track.java b/src/src/main/java/ownradio/domain/Track.java index 1721b74..45f4829 100644 --- a/src/src/main/java/ownradio/domain/Track.java +++ b/src/src/main/java/ownradio/domain/Track.java @@ -29,18 +29,4 @@ public class Track extends AbstractEntity { @Column(nullable = false) private String localdevicepathupload; - private Integer length; - - private String artist; - - private Integer size; - - private Integer iscorrect; - - private Integer isfilledinfo; - - private Integer iscensorial; - - private Integer isexist; - } diff --git a/src/src/main/java/ownradio/repository/TrackRepository.java b/src/src/main/java/ownradio/repository/TrackRepository.java index e525152..33af031 100644 --- a/src/src/main/java/ownradio/repository/TrackRepository.java +++ b/src/src/main/java/ownradio/repository/TrackRepository.java @@ -4,7 +4,6 @@ import org.springframework.data.jpa.repository.Query; import ownradio.domain.Track; -import java.util.List; import java.util.UUID; /** @@ -16,10 +15,6 @@ public interface TrackRepository extends JpaRepository { @Query(value = "select getnexttrackid_string(?1)", nativeQuery = true) UUID getNextTrackId(UUID deviceId); - @Query(value = "select * from getnexttrack(?1)", nativeQuery = true) -// @Query(value = "select * from getnexttrackid_v2(?1)", nativeQuery = true) - List getNextTrackV2(UUID deviceId); - @Query(value = "select registertrack(?1, ?2, ?3, ?4)", nativeQuery = true) boolean registerTrack(UUID trackId, String localDevicePathUpload, String path, UUID deviceId); } diff --git a/src/src/main/java/ownradio/service/TrackService.java b/src/src/main/java/ownradio/service/TrackService.java index 8bad137..f7aac70 100644 --- a/src/src/main/java/ownradio/service/TrackService.java +++ b/src/src/main/java/ownradio/service/TrackService.java @@ -1,11 +1,8 @@ package ownradio.service; import org.springframework.web.multipart.MultipartFile; -import ownradio.domain.NextTrack; import ownradio.domain.Track; -import java.util.Collection; -import java.util.List; import java.util.UUID; /** @@ -19,9 +16,5 @@ public interface TrackService { UUID getNextTrackId(UUID deviceId); - NextTrack getNextTrackIdV2(UUID deviceId); - void save(Track track, MultipartFile file); - - void setTrackInfo(UUID trackid); } diff --git a/src/src/main/java/ownradio/service/impl/TrackServiceImpl.java b/src/src/main/java/ownradio/service/impl/TrackServiceImpl.java index 3304f60..5e4936d 100644 --- a/src/src/main/java/ownradio/service/impl/TrackServiceImpl.java +++ b/src/src/main/java/ownradio/service/impl/TrackServiceImpl.java @@ -1,20 +1,15 @@ package ownradio.service.impl; -import com.mpatric.mp3agic.ID3v1; -import com.mpatric.mp3agic.ID3v2; -import com.mpatric.mp3agic.Mp3File; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.StringUtils; import org.springframework.web.multipart.MultipartFile; -import ownradio.domain.NextTrack; import ownradio.domain.Track; import ownradio.repository.TrackRepository; import ownradio.service.TrackService; import ownradio.util.ResourceUtil; -import java.util.List; import java.util.UUID; @Service @@ -39,24 +34,6 @@ public UUID getNextTrackId(UUID deviceId) { return trackRepository.getNextTrackId(deviceId); } - @Override - @Transactional - public NextTrack getNextTrackIdV2(UUID deviceId) { - NextTrack nextTrack = new NextTrack(); - List objects = trackRepository.getNextTrackV2(deviceId); - try{ - if(objects != null) { - nextTrack.setTrackid(UUID.fromString((String) objects.get(0)[0])); - nextTrack.setMethodid((Integer) objects.get(0)[1]); - return nextTrack; - }else{ - return null; - } - }catch (Exception ex){ - return null; - } - } - @Override @Transactional public void save(Track track, MultipartFile file) { @@ -73,54 +50,4 @@ public void save(Track track, MultipartFile file) { storeTrack.setPath(filePath); } - - @Override - @Transactional - public void setTrackInfo(UUID trackid) { - String artist = null; - String title = null; - boolean artistFlag = false; - boolean titleFlag = false; - - byte[] buf; - - if (trackid != null) { - try { - Track track = trackRepository.findOne(trackid); - Mp3File mp3File = new Mp3File(track.getPath()); - track.setLength((int) mp3File.getLengthInSeconds());//duration track - track.setSize((int) mp3File.getLength() / 1024);//size in kilobytes - - if (mp3File.hasId3v1Tag()) { - ID3v1 id3v1Tag1 = mp3File.getId3v1Tag(); - title = new String(id3v1Tag1.getTitle().getBytes("UTF16"),"Cp1251").replaceAll("\u0000", "").substring(2); -// title = id3v1Tag1.getTitle(); - artist = new String(id3v1Tag1.getArtist().getBytes("UTF16"),"Cp1251").replaceAll("\u0000", "").substring(2); -// artist = id3v1Tag1.getArtist(); - }else if (mp3File.hasId3v2Tag()) { - ID3v2 id3v2Tag2 = mp3File.getId3v2Tag(); - title = id3v2Tag2.getTitle(); - title = title.equals(id3v2Tag2.getTitle()) ? title : null; - artist = id3v2Tag2.getArtist(); - artist = artist.equals(id3v2Tag2.getArtist()) ? artist : null; - } - - if (title != null && !title.equals("null") && !title.isEmpty()) { - track.setRecname(title.replaceAll("\u0000", "")); - titleFlag = true; - } else - titleFlag = false; - if (artist != null && !artist.equals("null") && !artist.isEmpty()) { - track.setArtist(artist.replaceAll("\u0000", "")); - artistFlag = true; - } else - artistFlag = false; - - if (artistFlag && titleFlag) - track.setIsfilledinfo(1); - trackRepository.saveAndFlush(track); - } catch (Exception ex) { - } - } - } } diff --git a/src/src/main/java/ownradio/web/rest/v2/HistoryController.java b/src/src/main/java/ownradio/web/rest/v2/HistoryController.java index b0a000f..e972f1a 100644 --- a/src/src/main/java/ownradio/web/rest/v2/HistoryController.java +++ b/src/src/main/java/ownradio/web/rest/v2/HistoryController.java @@ -1,14 +1,10 @@ package ownradio.web.rest.v2; -import lombok.Data; -import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.format.annotation.DateTimeFormat; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.WebDataBinder; import org.springframework.web.bind.annotation.*; -import org.springframework.web.multipart.MultipartFile; import ownradio.domain.Device; import ownradio.domain.History; import ownradio.domain.Track; @@ -18,9 +14,7 @@ import ownradio.service.TrackService; import ownradio.service.UserService; -import javax.persistence.Column; import java.beans.PropertyEditorSupport; -import java.util.Date; import java.util.UUID; /** @@ -28,9 +22,8 @@ * * @author Alpenov Tanat */ -@Slf4j @RestController -@RequestMapping("/v2/histories") +@RequestMapping("/api/v2/histories") public class HistoryController { private final HistoryService historyService; private final TrackService trackService; @@ -44,10 +37,8 @@ public HistoryController(HistoryService historyService, TrackService trackServic } @RequestMapping(value = "/{deviceId}/{trackId}", method = RequestMethod.POST) - public ResponseEntity save(@PathVariable UUID deviceId, @PathVariable UUID trackId, @RequestBody History history) { + public ResponseEntity save(@PathVariable UUID deviceId, @PathVariable UUID trackId, History history) { try { - log.info("{} {}",deviceId.toString(),trackId.toString()); - log.info("{} {} {}",history.getLastListen(), history.getIsListen(), history.getMethod()); Track track = trackService.getById(trackId); Device device = deviceService.getById(deviceId); @@ -60,9 +51,9 @@ public ResponseEntity save(@PathVariable UUID deviceId, @PathVariable UUID track historyService.save(history); - return new ResponseEntity(history, HttpStatus.OK); + return new ResponseEntity(HttpStatus.OK); } catch (Exception e) { - return new ResponseEntity(history, HttpStatus.INTERNAL_SERVER_ERROR); + return new ResponseEntity(HttpStatus.INTERNAL_SERVER_ERROR); } } diff --git a/src/src/main/java/ownradio/web/rest/v2/TrackController.java b/src/src/main/java/ownradio/web/rest/v2/TrackController.java index 1dbd30f..beb0639 100644 --- a/src/src/main/java/ownradio/web/rest/v2/TrackController.java +++ b/src/src/main/java/ownradio/web/rest/v2/TrackController.java @@ -6,7 +6,10 @@ import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; import ownradio.domain.Device; import ownradio.domain.Track; @@ -22,7 +25,7 @@ */ @Slf4j @RestController -@RequestMapping(value = "/v2/tracks") +@RequestMapping(value = "/api/v2/tracks") public class TrackController { private final TrackService trackService; diff --git a/src/src/main/resources/application-dev.properties b/src/src/main/resources/application-dev.properties index 5814481..87765f0 100644 --- a/src/src/main/resources/application-dev.properties +++ b/src/src/main/resources/application-dev.properties @@ -6,7 +6,7 @@ spring.datasource.password= spring.datasource.platform=org.hibernate.dialect.H2Dialect spring.http.multipart.max-file-size=256Mb spring.http.multipart.max-request-size=256Mb -#spring.jackson.property-naming-strategy=CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES +spring.jackson.property-naming-strategy=CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES spring.jpa.properties.hibernate.format_sql=true spring.jpa.show-sql=true spring.jpa.hibernate.ddl-auto=update diff --git a/src/src/main/resources/application-prod.properties b/src/src/main/resources/application-prod.properties index ef6b7df..dde430b 100644 --- a/src/src/main/resources/application-prod.properties +++ b/src/src/main/resources/application-prod.properties @@ -1,9 +1,6 @@ -#spring.datasource.url=jdbc:postgresql://127.0.0.1:5432/ownradio -#spring.datasource.username=tanat -#spring.datasource.password=123456 -spring.datasource.url=jdbc:postgresql://10.10.0.67:5432/ownRadioJavaTest -spring.datasource.username=a.polunina -spring.datasource.password=6ImtqMGAt1 +spring.datasource.url=jdbc:postgresql://127.0.0.1:5432/ownradio +spring.datasource.username=tanat +spring.datasource.password=123456 spring.datasource.platform=org.hibernate.dialect.PostgreSQL94Dialect spring.datasource.test-while-idle=true spring.datasource.test-on-borrow=true @@ -12,7 +9,7 @@ spring.datasource.time-between-eviction-runs-millis=5000 spring.datasource.min-evictable-idle-time-millis=60000 spring.http.multipart.max-file-size=256Mb spring.http.multipart.max-request-size=256Mb -#spring.jackson.property-naming-strategy=CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES +spring.jackson.property-naming-strategy=CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES spring.jpa.properties.hibernate.format_sql=true spring.jpa.show-sql=true spring.jpa.hibernate.ddl-auto=update diff --git a/src/src/main/resources/data/postgresql/schema.sql b/src/src/main/resources/data/postgresql/schema.sql index 6806721..a9916e3 100644 --- a/src/src/main/resources/data/postgresql/schema.sql +++ b/src/src/main/resources/data/postgresql/schema.sql @@ -68,9 +68,9 @@ DECLARE i_ratingid UUID; BEGIN CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; - SELECT uuid_generate_v4() + SELECT uuid_generate_v1() INTO i_historyid; - SELECT uuid_generate_v4() + SELECT uuid_generate_v1() INTO i_ratingid; -- @@ -107,12 +107,12 @@ BEGIN END IF; -- Добавляем трек в базу данных - INSERT INTO tracks (recid, localdevicepathupload, path, deviceid, reccreated, isexist) - VALUES (i_trackid, i_localdevicepathupload, i_path, i_deviceid, now(), 1); + INSERT INTO tracks (recid, localdevicepathupload, path, deviceid, reccreated) + VALUES (i_trackid, i_localdevicepathupload, i_path, i_deviceid, now()); -- Добавляем запись о прослушивании трека в таблицу истории прослушивания - INSERT INTO histories (recid, deviceid, trackid, isListen, lastListen, methodid, reccreated) - VALUES (i_historyid, i_deviceid, i_trackid, 1, now(), 2, now()); + INSERT INTO histories (recid, deviceid, trackid, isListen, lastListen, method, reccreated) + VALUES (i_historyid, i_deviceid, i_trackid, 1, now(), ''method'', now()); -- Добавляем запись в таблицу рейтингов INSERT INTO ratings (recid, userid, trackid, lastListen, ratingsum, reccreated) @@ -122,184 +122,3 @@ BEGIN END; ' LANGUAGE plpgsql; - - -CREATE OR REPLACE FUNCTION getnexttrackid_v2(i_deviceid uuid) - RETURNS TABLE(track uuid, methodid integer) -AS -' -DECLARE - i_userid uuid = i_deviceid; - rnd integer = (select trunc(random() * 10)); -- получаем случайное число от 0 до 9 - o_methodid integer; -- id метода выбора трека -BEGIN - - -- Выбираем следующий трек - - -- В 9/10 случаях выбираем трек из треков пользователя (добавленных им или прослушанных до конца) - -- с положительным рейтингом, за исключением прослушанных за последние сутки - IF (rnd > 1) - THEN - o_methodid = 2; - RETURN QUERY - SELECT trackid, o_methodid - FROM ratings - WHERE userid = i_userid - AND lastlisten < localtimestamp - interval ''1 day'' - AND ratingsum >= 0 - AND (SELECT isexist FROM tracks WHERE recid = trackid) = 1 - ORDER BY RANDOM() - LIMIT 1; - - -- Если такой трек найден - выход из функции, возврат найденного значения - IF FOUND - THEN RETURN; - END IF; - END IF; - - -- В 1/10 случае выбираем случайный трек из ни разу не прослушанных пользователем треков - o_methodid = 3; - RETURN QUERY - SELECT recid, o_methodid - FROM tracks - WHERE recid NOT IN - (SELECT trackid - FROM ratings - WHERE userid = i_userid) - AND isexist = 1 - ORDER BY RANDOM() - LIMIT 1; - - -- Если такой трек найден - выход из функции, возврат найденного значения - IF FOUND - THEN RETURN; - END IF; - - -- Если предыдущие запросы вернули null, выбираем случайный трек - o_methodid = 1; - RETURN QUERY - SELECT recid, o_methodid - FROM tracks - WHERE isexist = 1 - ORDER BY RANDOM() - LIMIT 1; - RETURN; -END; -' -LANGUAGE plpgsql; - -CREATE OR REPLACE FUNCTION getnexttrackid_v3(IN i_deviceid uuid) - RETURNS TABLE(track uuid, methodid integer) AS -' -DECLARE - i_userid uuid = i_deviceid; - rnd integer = (select trunc(random() * 1001)); - o_methodid integer; -- id метода выбора трека - owntracks integer; -- количество "своих" треков пользователя (обрезаем на 900 шт) -BEGIN - -- Выбираем следующий трек - - -- Определяем количество "своих" треков пользователя, ограничивая его 900 - owntracks = (SELECT COUNT(*) FROM ( - SELECT * FROM ratings - WHERE userid = i_userid - AND ratingsum >=0 - LIMIT 900) AS count) ; - - -- Если rnd меньше количества "своих" треков, выбираем трек из треков пользователя (добавленных им или прослушанных до конца) - -- с положительным рейтингом, за исключением прослушанных за последние сутки - - IF (rnd < owntracks) - THEN - o_methodid = 2; -- метод выбора из своих треков - RETURN QUERY - SELECT trackid, o_methodid - FROM ratings - WHERE userid = i_userid - AND lastlisten < localtimestamp - interval ''1 day'' - AND ratingsum >= 0 - AND (SELECT isexist FROM tracks WHERE recid = trackid) = 1 - ORDER BY RANDOM() - LIMIT 1; - - -- Если такой трек найден - выход из функции, возврат найденного значения - IF FOUND - THEN RETURN; - END IF; - END IF; - - -- В 1/10 случае выбираем случайный трек из ни разу не прослушанных пользователем треков - o_methodid = 3; -- метод выбора из непрослушанных треков - RETURN QUERY - SELECT recid, o_methodid - FROM tracks - WHERE recid NOT IN - (SELECT trackid - FROM ratings - WHERE userid = i_userid) - AND isexist = 1 - ORDER BY RANDOM() - LIMIT 1; - - -- Если такой трек найден - выход из функции, возврат найденного значения - IF FOUND - THEN RETURN; - END IF; - - -- Если предыдущие запросы вернули null, выбираем случайный трек - o_methodid = 1; -- метод выбора случайного трека - RETURN QUERY - SELECT recid, o_methodid - FROM tracks - WHERE isexist = 1 - ORDER BY RANDOM() - LIMIT 1; - RETURN; -END; -' -LANGUAGE plpgsql; - -CREATE OR REPLACE FUNCTION getnexttrack(i_deviceid UUID) - RETURNS TABLE( - track character varying - , methodid integer) - AS -' -DECLARE - i_userid uuid = i_deviceid; -- в дальнейшем заменить получением userid по deviceid -BEGIN - -- Добавляем устройство, если его еще не существует - -- Если ID устройства еще нет в БД - IF NOT EXISTS(SELECT recid - FROM devices - WHERE recid = i_deviceid) - THEN - - -- Добавляем нового пользователя - INSERT INTO users (recid, recname, reccreated) SELECT - i_userid, - ''New user recname'', - now(); - - -- Добавляем новое устройство - INSERT INTO devices (recid, userid, recname, reccreated) SELECT - i_deviceid, - i_userid, - ''New device recname'', - now(); - ELSE - SELECT (SELECT userid - FROM devices - WHERE recid = i_deviceid - LIMIT 1) - INTO i_userid; - END IF; - - -- Возвращаем trackid, конвертируя его в character varying и methodid - RETURN QUERY SELECT - CAST((nexttrack.track) AS CHARACTER VARYING), - nexttrack.methodid - FROM getnexttrackid_v3(i_deviceid) AS nexttrack; -END; -' -LANGUAGE plpgsql; \ No newline at end of file diff --git a/src/src/test/java/ownradio/repository/HistoryRepositoryTest.java b/src/src/test/java/ownradio/repository/HistoryRepositoryTest.java index 4880970..6585d8d 100644 --- a/src/src/test/java/ownradio/repository/HistoryRepositoryTest.java +++ b/src/src/test/java/ownradio/repository/HistoryRepositoryTest.java @@ -12,7 +12,6 @@ import ownradio.domain.Track; import ownradio.domain.User; -import java.util.Calendar; import java.util.Date; import static org.hamcrest.CoreMatchers.nullValue; @@ -35,22 +34,22 @@ public class HistoryRepositoryTest { public void setUp() throws Exception { User user = entityManager.persist(new User()); Device device = entityManager.persist(new Device(user, "1")); - Track track = entityManager.persist(new Track("1", device, "1", 0, "", 0, null, null, null, 1)); + Track track = entityManager.persist(new Track("1", device, "1")); - history = new History(track, Calendar.getInstance(), 0, "post", 1, device); + history = new History(track, new Date(), 0, "post", device); entityManager.persist(history); } @Test public void createdAt() throws Exception { assertThat(history.getReccreated(), not(nullValue())); - assertThat(history.getReccreated().getTime().toString(), is(Calendar.getInstance().getTime().toString())); + assertThat(history.getReccreated().toString(), is(new Date().toString())); } @Test public void updatedAt() throws Exception { assertThat(history.getReccreated(), not(nullValue())); - assertThat(history.getReccreated().getTime().toString(), is(Calendar.getInstance().getTime().toString())); + assertThat(history.getReccreated().toString(), is(new Date().toString())); assertThat(history.getRecupdated(), is(nullValue())); History storeHistory = historyRepository.findOne(history.getRecid()); @@ -58,10 +57,10 @@ public void updatedAt() throws Exception { historyRepository.saveAndFlush(storeHistory); assertThat(storeHistory.getReccreated(), not(nullValue())); - assertThat(storeHistory.getReccreated().getTime().toString(), is(history.getReccreated().getTime().toString())); + assertThat(storeHistory.getReccreated().toString(), is(history.getReccreated().toString())); assertThat(storeHistory.getRecupdated(), not(nullValue())); - assertThat(storeHistory.getRecupdated().getTime().toString(), is(Calendar.getInstance().getTime().toString())); + assertThat(storeHistory.getRecupdated().toString(), is(new Date().toString())); } diff --git a/src/src/test/java/ownradio/repository/TrackRepositoryTest.java b/src/src/test/java/ownradio/repository/TrackRepositoryTest.java index b4fdc63..6c7618a 100644 --- a/src/src/test/java/ownradio/repository/TrackRepositoryTest.java +++ b/src/src/test/java/ownradio/repository/TrackRepositoryTest.java @@ -37,9 +37,9 @@ public class TrackRepositoryTest { public void setUp() throws Exception { user = entityManager.persist(new User()); device = entityManager.persist(new Device(user, "123")); - entityManager.persist(new Track("1", device, "1", 0, "", 0, null, null, null, 1)); - entityManager.persist(new Track("2", device, "1", 0, "", 0, null, null, null, 1)); - entityManager.persist(new Track("4", device, "1", 0, "", 0, null, null, null, 1)); + entityManager.persist(new Track("1", device, "1")); + entityManager.persist(new Track("2", device, "1")); + entityManager.persist(new Track("4", device, "1")); } @Test diff --git a/src/src/test/java/ownradio/web/rest/v2/HistoryControllerTest.java b/src/src/test/java/ownradio/web/rest/v2/HistoryControllerTest.java index be5ae70..b8b425c 100644 --- a/src/src/test/java/ownradio/web/rest/v2/HistoryControllerTest.java +++ b/src/src/test/java/ownradio/web/rest/v2/HistoryControllerTest.java @@ -1,14 +1,12 @@ package ownradio.web.rest.v2; import com.fasterxml.jackson.databind.ObjectMapper; -import org.json.JSONObject; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.boot.test.mock.mockito.MockBean; -import org.springframework.http.MediaType; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.web.servlet.MockMvc; import ownradio.domain.Device; @@ -70,17 +68,10 @@ public void saveStatusIsOk() throws Exception { given(this.trackService.getById(TRACK_UUID)).willReturn(track); given(this.deviceService.getById(DEVICE_UUID)).willReturn(device); - JSONObject obj = new JSONObject(); - obj.put("lastListen", "2016-11-28T12:34:56"); - obj.put("isListen", "1"); - obj.put("method", "method"); - - mockMvc.perform(post("/v2/histories/{deviceId}/{trackId}", DEVICE_UUID, TRACK_UUID) - .contentType(MediaType.APPLICATION_JSON) - .content(obj.toString()) -// .param("lastListen", "2016-11-28 12:34:56") -// .param("isListen", "1") -// .param("method", "method") + mockMvc.perform(post("/api/v2/histories/{deviceId}/{trackId}", DEVICE_UUID, TRACK_UUID) + .param("lastListen", "12/12/2016 11:11:11") + .param("isListen", "1") + .param("method", "method") ) .andDo(print()) .andExpect( @@ -96,17 +87,10 @@ public void saveStatusIsInternalServerError() throws Exception { doThrow(RuntimeException.class).when(this.historyService).save(any(History.class)); - JSONObject obj = new JSONObject(); - obj.put("lastListen", "2016-11-28T12:34:56"); - obj.put("isListen", "1"); - obj.put("method", "method"); - - mockMvc.perform(post("/v2/histories/{deviceId}/{trackId}", DEVICE_UUID, TRACK_UUID) - .contentType(MediaType.APPLICATION_JSON) - .content(obj.toString()) -// .param("lastListen", "2016-11-28 12:34:56") -// .param("isListen", "1") -// .param("method", "method") + mockMvc.perform(post("/api/v2/histories/{deviceId}/{trackId}", DEVICE_UUID, TRACK_UUID) + .param("lastListen", "12/12/2016 11:11:11") + .param("isListen", "1") + .param("method", "method") ) .andDo(print()) .andExpect( diff --git a/src/src/test/java/ownradio/web/rest/v2/TrackControllerTest.java b/src/src/test/java/ownradio/web/rest/v2/TrackControllerTest.java index b36bcb6..138a18f 100644 --- a/src/src/test/java/ownradio/web/rest/v2/TrackControllerTest.java +++ b/src/src/test/java/ownradio/web/rest/v2/TrackControllerTest.java @@ -2,7 +2,6 @@ import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.tomcat.util.http.fileupload.FileUtils; -import org.json.JSONObject; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -67,7 +66,7 @@ public void setUp() throws Exception { device.setRecid(DEVICE_UUID); device.setUser(user); - track = new Track(PATH, device, "---", 0, "", 0, null, null, null, 1); + track = new Track(PATH, device, "---"); String requestParam = "musicFile"; String originalFilename = "test.mp3"; @@ -88,21 +87,13 @@ public void tearDown() throws Exception { public void saveStatusIsOk() throws Exception { given(this.deviceService.getById(DEVICE_UUID)).willReturn(device); - JSONObject obj = new JSONObject(); - obj.put("fileGuid", TRACK_UUID.toString()); - obj.put("fileName", correctFile.getOriginalFilename()); - obj.put("filePath", PATH); - obj.put("deviceId", DEVICE_UUID.toString()); - - mockMvc.perform(fileUpload("/v2/tracks") + mockMvc.perform(fileUpload("/api/v2/tracks") .file(correctFile) .accept(MediaType.APPLICATION_JSON_UTF8_VALUE) - .content(obj.toString()) - .contentType(MediaType.MULTIPART_FORM_DATA) -// .param("fileGuid", TRACK_UUID.toString()) -// .param("fileName", correctFile.getOriginalFilename()) -// .param("filePath", PATH) -// .param("deviceId", DEVICE_UUID.toString()) + .param("fileGuid", TRACK_UUID.toString()) + .param("fileName", correctFile.getOriginalFilename()) + .param("filePath", PATH) + .param("deviceId", DEVICE_UUID.toString()) ) .andDo(print()) .andExpect( @@ -112,7 +103,7 @@ public void saveStatusIsOk() throws Exception { @Test public void saveStatusIsBadRequest() throws Exception { - mockMvc.perform(fileUpload("/v2/tracks") + mockMvc.perform(fileUpload("/api/v2/tracks") .file(emptyFile) .accept(MediaType.APPLICATION_JSON_UTF8_VALUE) .param("fileGuid", TRACK_UUID.toString()) @@ -130,7 +121,7 @@ public void saveStatusIsBadRequest() throws Exception { public void getTrackStatusIsOk() throws Exception { given(this.trackService.getById(TRACK_UUID)).willReturn(track); - mockMvc.perform(get("/v2/tracks/{trackId}", TRACK_UUID).accept(MediaType.TEXT_PLAIN)) + mockMvc.perform(get("/api/v2/tracks/{trackId}", TRACK_UUID).accept(MediaType.TEXT_PLAIN)) .andDo(print()) .andExpect( status().isOk() @@ -148,7 +139,7 @@ public void getTrackStatusIsNotFound() throws Exception { given(this.trackService.getById(TRACK_UUID)).willReturn(null); - mockMvc.perform(get("/v2/tracks/{trackId}", TRACK_UUID).accept(MediaType.TEXT_PLAIN)) + mockMvc.perform(get("/api/v2/tracks/{trackId}", TRACK_UUID).accept(MediaType.TEXT_PLAIN)) .andDo(print()) .andExpect( status().isNotFound() @@ -159,7 +150,7 @@ public void getTrackStatusIsNotFound() throws Exception { public void getNextTrackIdIsOk() throws Exception { given(this.trackService.getNextTrackId(DEVICE_UUID)).willReturn(TRACK_UUID); - mockMvc.perform(get("/v2/tracks/{deviceId}/next", DEVICE_UUID)) + mockMvc.perform(get("/api/v2/tracks/{deviceId}/next", DEVICE_UUID)) .andDo(print()) .andExpect( status().isOk() @@ -174,7 +165,7 @@ public void getNextTrackIdIsOk() throws Exception { public void getNextTrackIdIsNotFound() throws Exception { given(this.trackService.getNextTrackId(DEVICE_UUID)).willReturn(null); - mockMvc.perform(get("/v2/tracks/{deviceId}/next", DEVICE_UUID)) + mockMvc.perform(get("/api/v2/tracks/{deviceId}/next", DEVICE_UUID)) .andDo(print()) .andExpect( status().isNotFound() diff --git a/v3/.gitignore b/v3/.gitignore new file mode 100644 index 0000000..eb86bec --- /dev/null +++ b/v3/.gitignore @@ -0,0 +1,6 @@ +.idea/ +logs/ +target/ +userfile/ +*.iml + diff --git a/v3/.mvn/wrapper/maven-wrapper.jar b/v3/.mvn/wrapper/maven-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..5fd4d5023f1463b5ba3970e33c460c1eb26d748d GIT binary patch literal 49502 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%0A$pwhjXzyA0ZzErH{1@M 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 OJOEd-=rxTO#Qy`$*1Hh^ literal 0 HcmV?d00001 diff --git a/v3/.mvn/wrapper/maven-wrapper.properties b/v3/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000..c954cec --- /dev/null +++ b/v3/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1 @@ +distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.3.9/apache-maven-3.3.9-bin.zip diff --git a/v3/README.md b/v3/README.md new file mode 100644 index 0000000..d663e2b --- /dev/null +++ b/v3/README.md @@ -0,0 +1,113 @@ +# Java сервер для OwnRadio + +Этот проект разрабатывается с целью тестирования и обкатки Java технологий + +Run +--- +##### Запускаем с помощью maven +* $ mvn -Dupload.dir=c:\ -Dspring.profiles.active=dev -Dserver.port=8080 spring-boot:run +* $ mvn -Dupload.dir=c:\ -Dspring.profiles.active=prod -Dserver.port=8080 spring-boot:run + +##### Запускаем упакованный jar +* $ java -Dupload.dir=c:\ -jar ownradio.jar --spring.profiles.active=dev --server.port=8080 +* $ java -Dupload.dir=c:\ -jar ownradio.jar --spring.profiles.active=prod --server.port=8080 + + +Web API +--- + +### Загрузка трека на сервер + +##### POST /v2/tracks +* `fileGuid` – UUID трека +* `fileName` – имя файла +* `filePath` - Локальный путь к файлу на пользовательском устройстве +* `deviceId` – UUID device +* `musicFile` – прикрепленный файл + +##### HttpStatus +* `400, "Bad Request"` - Если пользователь ввел некорректные данные +* `201, "Created"` – если все ок +* `500, "Internal Server Error"` – если произошел сбой на сервере + +### Получение трека с сервера +##### GET /v2/tracks/{trackId} +* `{trackId}` – UUID трека + +##### HttpStatus +* `200, "OK"` – в теле ответа будет лежать трек +* `404, "Not Found"` – если трек с таким recid не найден + +### Получение следующего трека с сервера +##### GET /v2/tracks/{deviceId}/next +* `{deviceId}` – UUID девайса + +##### HttpStatus +* `200, "OK"` – в теле ответа будет лежать UUID трека +* `404, "Not Found"` – если трек с таким recid не найден + +### Сохранение истории треков +##### POST /v2/histories/{deviceId}/{trackId} +* `{trackId}` – UUID прослушанного трека +* `{deviceId}` – UUID устройства где был прослушан трек +* `lastListen` - Время последнего прослушивания или пропуска трека для данного пользователя +* `isListen` - Признак прослушан ли трек до конца: 1 - прослушан, -1 – нет +* `method` - Метод выбора трека + +##### HttpStatus +* `200, "OK"` – если все ок +* `500, "Internal Server Error"` – если произошел сбой на сервере + +Web API v3 +--- + +### Загрузка трека на сервер + +##### POST /v3/tracks +* `fileGuid` – UUID трека +* `filePath` - Полный локальный путь к файлу на пользовательском устройстве, включая имя файла (String) +* `deviceId` – UUID device +* `musicFile` – прикрепленный файл + +##### HttpStatus +* `400, "Bad Request"` - Если пользователь ввел некорректные данные +* `201, "Created"` – если все ок +* `500, "Internal Server Error"` – если произошел сбой на сервере + +### Получение трека с сервера +##### GET /v3/tracks/{trackId} +* `{trackId}` – UUID трека + +##### HttpStatus +* `200, "OK"` – в теле ответа будет лежать трек +* `404, "Not Found"` – если трек с таким recid не найден + +### Получение следующего трека с сервера +##### GET /v3/tracks/{deviceId}/next +* `{deviceId}` – UUID девайса + +##### Response +Content-Type →application/json;charset=UTF-8 +{ + "artist": "Artist", + "length": "duration", + "name": "Title", + "methodid": "1", + "id": "00000000-0000-0000-0000-000000000000" +} + +##### HttpStatus +* `200, "OK"` – в теле ответа будет лежать UUID трека +* `404, "Not Found"` – если трек с таким recid не найден + +### Сохранение истории треков +##### POST /v3/histories/{deviceId}/{trackId} +* `{trackId}` – UUID прослушанного трека +* `{deviceId}` – UUID устройства где был прослушан трек +* `lastListen` - Время последнего прослушивания или пропуска трека для данного пользователя ("yyyy-MM-ddTHH:mm:ss") +* `isListen` - Признак прослушан ли трек до конца: 1 - прослушан, -1 – нет (int) +* `methodid` - ID метода выбора трека (int), равен значению, полученному при получении данных для следующего трека + +##### HttpStatus +* `200, "OK"` – если все ок +* `500, "Internal Server Error"` – если произошел сбой на сервере diff --git a/v3/msg/masseage_en.properties b/v3/msg/masseage_en.properties new file mode 100644 index 0000000..2484f99 --- /dev/null +++ b/v3/msg/masseage_en.properties @@ -0,0 +1 @@ +id=\u041D\u0430\u0437\u0432\u0430\u043D\u0438\u0435 \u0438\u0434\u0435\u043D\u0442\u0438\u0444\u0438\u043A\u0430\u0442\u043E\u0440\u0430 \u043D\u0430 en diff --git a/v3/msg/masseage_ru.properties b/v3/msg/masseage_ru.properties new file mode 100644 index 0000000..fd108d6 --- /dev/null +++ b/v3/msg/masseage_ru.properties @@ -0,0 +1 @@ +id=\u041D\u0430\u0437\u0432\u0430\u043D\u0438\u0435 \u0438\u0434\u0435\u043D\u0442\u0438\u0444\u0438\u043A\u0430\u0442\u043E\u0440\u0430 \u043D\u0430 ru diff --git a/v3/mvnw b/v3/mvnw new file mode 100644 index 0000000..a1ba1bf --- /dev/null +++ b/v3/mvnw @@ -0,0 +1,233 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven2 Start Up Batch script +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# M2_HOME - location of maven2's installed home dir +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "`uname`" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # + # Look for the Apple JDKs first to preserve the existing behaviour, and then look + # for the new JDKs provided by Oracle. + # + if [ -z "$JAVA_HOME" ] && [ -L /System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK ] ; then + # + # Apple JDKs + # + export JAVA_HOME=/System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK/Home + fi + + if [ -z "$JAVA_HOME" ] && [ -L /System/Library/Java/JavaVirtualMachines/CurrentJDK ] ; then + # + # Apple JDKs + # + export JAVA_HOME=/System/Library/Java/JavaVirtualMachines/CurrentJDK/Contents/Home + fi + + if [ -z "$JAVA_HOME" ] && [ -L "/Library/Java/JavaVirtualMachines/CurrentJDK" ] ; then + # + # Oracle JDKs + # + export JAVA_HOME=/Library/Java/JavaVirtualMachines/CurrentJDK/Contents/Home + fi + + if [ -z "$JAVA_HOME" ] && [ -x "/usr/libexec/java_home" ]; then + # + # Apple JDKs + # + export JAVA_HOME=`/usr/libexec/java_home` + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=`java-config --jre-home` + fi +fi + +if [ -z "$M2_HOME" ] ; then + ## resolve links - $0 may be a link to maven's home + PRG="$0" + + # need this for relative symlinks + while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG="`dirname "$PRG"`/$link" + fi + done + + saveddir=`pwd` + + M2_HOME=`dirname "$PRG"`/.. + + # make it fully qualified + M2_HOME=`cd "$M2_HOME" && pwd` + + cd "$saveddir" + # echo Using m2 at $M2_HOME +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --unix "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +fi + +# For Migwn, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$M2_HOME" ] && + M2_HOME="`(cd "$M2_HOME"; pwd)`" + [ -n "$JAVA_HOME" ] && + JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" + # TODO classpath? +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="`which javac`" + if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=`which readlink` + if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + if $darwin ; then + javaHome="`dirname \"$javaExecutable\"`" + javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + else + javaExecutable="`readlink -f \"$javaExecutable\"`" + fi + javaHome="`dirname \"$javaExecutable\"`" + javaHome=`expr "$javaHome" : '\(.*\)/bin'` + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="`which java`" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --path --windows "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --windows "$CLASSPATH"` +fi + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + local basedir=$(pwd) + local wdir=$(pwd) + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + wdir=$(cd "$wdir/.."; pwd) + done + echo "${basedir}" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-$(find_maven_basedir)} +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +exec "$JAVACMD" \ + $MAVEN_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} "$@" diff --git a/v3/mvnw.cmd b/v3/mvnw.cmd new file mode 100644 index 0000000..2b934e8 --- /dev/null +++ b/v3/mvnw.cmd @@ -0,0 +1,145 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Maven2 Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM enable echoing my setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" +if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +set MAVEN_CMD_LINE_ARGS=%* + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" + +set WRAPPER_JAR="".\.mvn\wrapper\maven-wrapper.jar"" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CMD_LINE_ARGS% +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" +if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%" == "on" pause + +if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% + +exit /B %ERROR_CODE% \ No newline at end of file diff --git a/v3/pom.xml b/v3/pom.xml new file mode 100644 index 0000000..3362a95 --- /dev/null +++ b/v3/pom.xml @@ -0,0 +1,80 @@ + + + 4.0.0 + + kz.tanat + ownradio + 0.0.1-SNAPSHOT + jar + + OwnRadio + Серверная часть системы OwnRadio + + + org.springframework.boot + spring-boot-starter-parent + 1.4.0.RELEASE + + + + + UTF-8 + UTF-8 + 1.8 + + + + + org.springframework.boot + spring-boot-starter-data-jpa + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-devtools + true + + + org.projectlombok + lombok + + + + com.h2database + h2 + runtime + + + org.postgresql + postgresql + runtime + + + org.springframework.boot + spring-boot-starter-test + test + + + + com.mpatric + mp3agic + 0.8.4 + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + + diff --git a/v3/src/main/java/ownradio/Application.java b/v3/src/main/java/ownradio/Application.java new file mode 100644 index 0000000..3f37ad8 --- /dev/null +++ b/v3/src/main/java/ownradio/Application.java @@ -0,0 +1,25 @@ +package ownradio; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.ConfigurableApplicationContext; + +/** + * Главный класс приложения + * + * @author Alpenov Tanat + */ +@Slf4j +@SpringBootApplication +public class Application { + + public static void main(String[] args) { + final ConfigurableApplicationContext context = SpringApplication.run(Application.class, args); + + if (context.getEnvironment().getActiveProfiles().length > 0 && + "dev".equals(context.getEnvironment().getActiveProfiles()[0])) { + log.info("Open in browser: " + context.getEnvironment().getProperty("this-url")); + } + } +} diff --git a/v3/src/main/java/ownradio/annotation/DisplayName.java b/v3/src/main/java/ownradio/annotation/DisplayName.java new file mode 100644 index 0000000..0ef5e15 --- /dev/null +++ b/v3/src/main/java/ownradio/annotation/DisplayName.java @@ -0,0 +1,24 @@ +package ownradio.annotation; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * Аннотация предназначена для хранения метаинформации по полю класса + * key - хранит ключ строкового значения который в зависимости от выборной лакали указывает на строку с нужным переводом + * isVisible – хранит значение видимости поля + * + * @author Alpenov Tanat + */ +@Target({METHOD, FIELD}) +@Retention(RUNTIME) +public @interface DisplayName { + + String key() default ""; + + boolean isVisible() default true; +} \ No newline at end of file diff --git a/v3/src/main/java/ownradio/command/InitDb.java b/v3/src/main/java/ownradio/command/InitDb.java new file mode 100644 index 0000000..ab61a8b --- /dev/null +++ b/v3/src/main/java/ownradio/command/InitDb.java @@ -0,0 +1,31 @@ +package ownradio.command; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.CommandLineRunner; +import org.springframework.context.annotation.Profile; +import org.springframework.stereotype.Component; +import ownradio.domain.User; +import ownradio.service.UserService; + +import java.util.TimeZone; + +/** + * Класс инициализирует БД первоначальными данными + * + * @author Alpenov Tanat + */ +@Slf4j +@Profile("dev") +@Component +public class InitDb implements CommandLineRunner { + @Autowired + private UserService userService; + + @Override + public void run(String... strings) throws Exception { + TimeZone.setDefault(TimeZone.getTimeZone("Etc/UTC")); + User user = userService.save(new User()); + log.debug("User recid: {}", user.getRecid()); + } +} diff --git a/v3/src/main/java/ownradio/domain/AbstractEntity.java b/v3/src/main/java/ownradio/domain/AbstractEntity.java new file mode 100644 index 0000000..991ae20 --- /dev/null +++ b/v3/src/main/java/ownradio/domain/AbstractEntity.java @@ -0,0 +1,65 @@ +package ownradio.domain; + +import lombok.Getter; +import lombok.Setter; +import org.hibernate.annotations.GenericGenerator; +import ownradio.annotation.DisplayName; + +import javax.persistence.*; +import java.io.Serializable; +import java.util.Calendar; +import java.util.Date; +import java.util.UUID; + +/** + * Базовый класс для всех сущностей + * Предназначен для хранения технической информации + * + * @author Alpenov Tanat + */ +@Getter +@Setter +@MappedSuperclass +public abstract class AbstractEntity implements Serializable { + + @DisplayName(key = "id") + @Id + @GeneratedValue(generator = "uuid") + @GenericGenerator(name = "uuid", strategy = "uuid2") + @Column(unique = true) + private UUID recid; + + private String recname; + + @Temporal(TemporalType.TIMESTAMP) + private Calendar reccreated; + + @Temporal(TemporalType.TIMESTAMP) + private Calendar recupdated; + + @PrePersist + public void beforePersist() { + setReccreated(Calendar.getInstance()); + } + + @PreUpdate + public void beforeUpdate() { + setRecupdated(Calendar.getInstance()); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + AbstractEntity that = (AbstractEntity) o; + + return recid != null ? recid.equals(that.recid) : that.recid == null; + + } + + @Override + public int hashCode() { + return recid != null ? recid.hashCode() : 0; + } +} diff --git a/v3/src/main/java/ownradio/domain/Device.java b/v3/src/main/java/ownradio/domain/Device.java new file mode 100644 index 0000000..e6910df --- /dev/null +++ b/v3/src/main/java/ownradio/domain/Device.java @@ -0,0 +1,34 @@ +package ownradio.domain; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import javax.persistence.Entity; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.Table; + +/** + * Сущность для хранения информации о девайсе + * + * @author Alpenov Tanat + */ +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@Entity +@Table(name = "devices") +public class Device extends AbstractEntity { + @ManyToOne + @JoinColumn(name = "userid") + private User user; + +// private String name; + public Device (User user, String name){ + setRecname(name); + setUser(user); + } +} diff --git a/v3/src/main/java/ownradio/domain/DownloadTrack.java b/v3/src/main/java/ownradio/domain/DownloadTrack.java new file mode 100644 index 0000000..447832d --- /dev/null +++ b/v3/src/main/java/ownradio/domain/DownloadTrack.java @@ -0,0 +1,30 @@ +package ownradio.domain; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import javax.persistence.*; + +/** + * Сущность для хранения информации о скаченных треках + * + * @author Alpenov Tanat + */ +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@Entity +@Table(name = "downloadtracks") +public class DownloadTrack extends AbstractEntity { + @ManyToOne + @JoinColumn(name = "deviceid") + private Device device; + + @ManyToOne + @JoinColumn(name = "trackid") + private Track track; + +} diff --git a/v3/src/main/java/ownradio/domain/History.java b/v3/src/main/java/ownradio/domain/History.java new file mode 100644 index 0000000..679afa3 --- /dev/null +++ b/v3/src/main/java/ownradio/domain/History.java @@ -0,0 +1,47 @@ +package ownradio.domain; + + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.springframework.format.annotation.DateTimeFormat; + +import javax.persistence.*; +import java.util.Calendar; +import java.util.Date; + +/** + * Сущность для хранения информации о прослушанных треках + * + * @author Alpenov Tanat + */ +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@Entity +@Table(name = "histories") +public class History extends AbstractEntity { + + @ManyToOne + @JoinColumn(name = "trackid") + private Track track; + + @DateTimeFormat(pattern = "dd-MM-yyyy'T'H:m:s") + @Temporal(TemporalType.TIMESTAMP) + @Column(name = "lastlisten", nullable = false) + private Calendar lastListen; + + @Column(name = "islisten", nullable = false) + private int isListen; // 1, -1 + +// @Column(nullable = false) + private String method; + + private Integer methodid; + + @ManyToOne + @JoinColumn(name = "deviceid") + private Device device; +} diff --git a/src/src/main/java/ownradio/domain/NextTrack.java b/v3/src/main/java/ownradio/domain/NextTrack.java similarity index 100% rename from src/src/main/java/ownradio/domain/NextTrack.java rename to v3/src/main/java/ownradio/domain/NextTrack.java diff --git a/v3/src/main/java/ownradio/domain/Rating.java b/v3/src/main/java/ownradio/domain/Rating.java new file mode 100644 index 0000000..02e753f --- /dev/null +++ b/v3/src/main/java/ownradio/domain/Rating.java @@ -0,0 +1,41 @@ +package ownradio.domain; + + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.springframework.format.annotation.DateTimeFormat; + +import javax.persistence.*; +import java.util.Calendar; +import java.util.Date; + +/** + * Сущность для хранения информации о рейтингах треков + * + * @author Alpenov Tanat + */ +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@Entity +@Table(name = "ratings") +public class Rating extends AbstractEntity { + @ManyToOne + @JoinColumn(name = "userid") + private User user; + + @ManyToOne + @JoinColumn(name = "trackid") + private Track track; + + @DateTimeFormat(pattern = "dd-MM-yyyy'T'H:m:s") + @Column(nullable = false) + @Temporal(TemporalType.TIMESTAMP) + private Calendar lastlisten; + + @Column(nullable = false) + private Integer ratingsum; +} diff --git a/v3/src/main/java/ownradio/domain/Track.java b/v3/src/main/java/ownradio/domain/Track.java new file mode 100644 index 0000000..1721b74 --- /dev/null +++ b/v3/src/main/java/ownradio/domain/Track.java @@ -0,0 +1,46 @@ +package ownradio.domain; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import javax.persistence.*; + +/** + * Сущность для хранения информации о треке + * + * @author Alpenov Tanat + */ +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@Entity +@Table(name = "tracks") +public class Track extends AbstractEntity { + + private String path; + + @ManyToOne + @JoinColumn(name = "deviceid") + private Device device; + + @Column(nullable = false) + private String localdevicepathupload; + + private Integer length; + + private String artist; + + private Integer size; + + private Integer iscorrect; + + private Integer isfilledinfo; + + private Integer iscensorial; + + private Integer isexist; + +} diff --git a/v3/src/main/java/ownradio/domain/User.java b/v3/src/main/java/ownradio/domain/User.java new file mode 100644 index 0000000..1f1c8c9 --- /dev/null +++ b/v3/src/main/java/ownradio/domain/User.java @@ -0,0 +1,24 @@ +package ownradio.domain; + +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import javax.persistence.Entity; +import javax.persistence.Table; + +/** + * Сущность для хранения информации о пользователе + * + * @author Alpenov Tanat + */ +@Getter +@Setter +@NoArgsConstructor +@Entity +@Table(name = "users") +public class User extends AbstractEntity { + public User(String name) { + setRecname(name); + } +} diff --git a/v3/src/main/java/ownradio/repository/DeviceRepository.java b/v3/src/main/java/ownradio/repository/DeviceRepository.java new file mode 100644 index 0000000..a7c0877 --- /dev/null +++ b/v3/src/main/java/ownradio/repository/DeviceRepository.java @@ -0,0 +1,14 @@ +package ownradio.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import ownradio.domain.Device; + +import java.util.UUID; + +/** + * Интерфейс репозитория, для хранения девайсов + * + * @author Alpenov Tanat + */ +public interface DeviceRepository extends JpaRepository { +} diff --git a/v3/src/main/java/ownradio/repository/DownloadTrackRepository.java b/v3/src/main/java/ownradio/repository/DownloadTrackRepository.java new file mode 100644 index 0000000..ef61be7 --- /dev/null +++ b/v3/src/main/java/ownradio/repository/DownloadTrackRepository.java @@ -0,0 +1,14 @@ +package ownradio.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import ownradio.domain.DownloadTrack; + +import java.util.UUID; + +/** + * Интерфейс репозитория, для хранения истории скаченных треков + * + * @author Alpenov Tanat + */ +public interface DownloadTrackRepository extends JpaRepository { +} diff --git a/v3/src/main/java/ownradio/repository/HistoryRepository.java b/v3/src/main/java/ownradio/repository/HistoryRepository.java new file mode 100644 index 0000000..50251fc --- /dev/null +++ b/v3/src/main/java/ownradio/repository/HistoryRepository.java @@ -0,0 +1,14 @@ +package ownradio.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import ownradio.domain.History; + +import java.util.UUID; + +/** + * Интерфейс репозитория, для хранения истории прослушанных треков + * + * @author Alpenov Tanat + */ +public interface HistoryRepository extends JpaRepository { +} diff --git a/v3/src/main/java/ownradio/repository/RatingRepository.java b/v3/src/main/java/ownradio/repository/RatingRepository.java new file mode 100644 index 0000000..b739402 --- /dev/null +++ b/v3/src/main/java/ownradio/repository/RatingRepository.java @@ -0,0 +1,22 @@ +package ownradio.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import ownradio.domain.Rating; +import ownradio.domain.Track; +import ownradio.domain.User; + +import java.util.Date; +import java.util.UUID; + +/** + * Интерфейс репозитория, для хранения рейтингов прослушанных треков + * + * @author Alpenov Tanat + */ +public interface RatingRepository extends JpaRepository { + + Rating findByUser(User user); + + Rating findByUserAndTrack(User user, Track track); +} diff --git a/v3/src/main/java/ownradio/repository/TrackRepository.java b/v3/src/main/java/ownradio/repository/TrackRepository.java new file mode 100644 index 0000000..e525152 --- /dev/null +++ b/v3/src/main/java/ownradio/repository/TrackRepository.java @@ -0,0 +1,25 @@ +package ownradio.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import ownradio.domain.Track; + +import java.util.List; +import java.util.UUID; + +/** + * Интерфейс репозитория, для хранения треков + * + * @author Alpenov Tanat + */ +public interface TrackRepository extends JpaRepository { + @Query(value = "select getnexttrackid_string(?1)", nativeQuery = true) + UUID getNextTrackId(UUID deviceId); + + @Query(value = "select * from getnexttrack(?1)", nativeQuery = true) +// @Query(value = "select * from getnexttrackid_v2(?1)", nativeQuery = true) + List getNextTrackV2(UUID deviceId); + + @Query(value = "select registertrack(?1, ?2, ?3, ?4)", nativeQuery = true) + boolean registerTrack(UUID trackId, String localDevicePathUpload, String path, UUID deviceId); +} diff --git a/v3/src/main/java/ownradio/repository/UserRepository.java b/v3/src/main/java/ownradio/repository/UserRepository.java new file mode 100644 index 0000000..952596e --- /dev/null +++ b/v3/src/main/java/ownradio/repository/UserRepository.java @@ -0,0 +1,14 @@ +package ownradio.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import ownradio.domain.User; + +import java.util.UUID; + +/** + * Интерфейс репозитория, для хранения данных пользователя + * + * @author Alpenov Tanat + */ +public interface UserRepository extends JpaRepository { +} diff --git a/v3/src/main/java/ownradio/service/DeviceService.java b/v3/src/main/java/ownradio/service/DeviceService.java new file mode 100644 index 0000000..10974e6 --- /dev/null +++ b/v3/src/main/java/ownradio/service/DeviceService.java @@ -0,0 +1,16 @@ +package ownradio.service; + +import ownradio.domain.Device; + +import java.util.UUID; + +/** + * Интерфейс сервиса, для работы с девайсами + * + * @author Alpenov Tanat + */ +public interface DeviceService { + void save(Device device); + + Device getById(UUID uuid); +} diff --git a/v3/src/main/java/ownradio/service/DownloadTrackService.java b/v3/src/main/java/ownradio/service/DownloadTrackService.java new file mode 100644 index 0000000..7c0b3f4 --- /dev/null +++ b/v3/src/main/java/ownradio/service/DownloadTrackService.java @@ -0,0 +1,12 @@ +package ownradio.service; + +import ownradio.domain.DownloadTrack; + +/** + * Интерфейс сервиса, для работы с историей скаченных треков + * + * @author Alpenov Tanat + */ +public interface DownloadTrackService { + void save(DownloadTrack downloadTrack); +} diff --git a/v3/src/main/java/ownradio/service/HistoryService.java b/v3/src/main/java/ownradio/service/HistoryService.java new file mode 100644 index 0000000..6cccb29 --- /dev/null +++ b/v3/src/main/java/ownradio/service/HistoryService.java @@ -0,0 +1,12 @@ +package ownradio.service; + +import ownradio.domain.History; + +/** + * Интерфейс сервиса, для работы с историей прослушанных треков + * + * @author Alpenov Tanat + */ +public interface HistoryService { + void save(History history); +} diff --git a/v3/src/main/java/ownradio/service/RatingService.java b/v3/src/main/java/ownradio/service/RatingService.java new file mode 100644 index 0000000..b06a3da --- /dev/null +++ b/v3/src/main/java/ownradio/service/RatingService.java @@ -0,0 +1,12 @@ +package ownradio.service; + +import ownradio.domain.Rating; + +/** + * Интерфейс сервиса, для работы с рейтингами прослушанных треков + * + * @author Alpenov Tanat + */ +public interface RatingService { + void save(Rating rating); +} diff --git a/v3/src/main/java/ownradio/service/TrackService.java b/v3/src/main/java/ownradio/service/TrackService.java new file mode 100644 index 0000000..8bad137 --- /dev/null +++ b/v3/src/main/java/ownradio/service/TrackService.java @@ -0,0 +1,27 @@ +package ownradio.service; + +import org.springframework.web.multipart.MultipartFile; +import ownradio.domain.NextTrack; +import ownradio.domain.Track; + +import java.util.Collection; +import java.util.List; +import java.util.UUID; + +/** + * Интерфейс сервиса, для работы с треками + * + * @author Alpenov Tanat + */ +public interface TrackService { + + Track getById(UUID id); + + UUID getNextTrackId(UUID deviceId); + + NextTrack getNextTrackIdV2(UUID deviceId); + + void save(Track track, MultipartFile file); + + void setTrackInfo(UUID trackid); +} diff --git a/v3/src/main/java/ownradio/service/UserService.java b/v3/src/main/java/ownradio/service/UserService.java new file mode 100644 index 0000000..54037f3 --- /dev/null +++ b/v3/src/main/java/ownradio/service/UserService.java @@ -0,0 +1,16 @@ +package ownradio.service; + +import ownradio.domain.User; + +import java.util.UUID; + +/** + * Интерфейс сервиса, для работы с пользовательскими данными + * + * @author Alpenov Tanat + */ +public interface UserService { + User getById(UUID id); + + User save(User user); +} diff --git a/v3/src/main/java/ownradio/service/impl/DeviceServiceImpl.java b/v3/src/main/java/ownradio/service/impl/DeviceServiceImpl.java new file mode 100644 index 0000000..d6b6a0a --- /dev/null +++ b/v3/src/main/java/ownradio/service/impl/DeviceServiceImpl.java @@ -0,0 +1,31 @@ +package ownradio.service.impl; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import ownradio.domain.Device; +import ownradio.repository.DeviceRepository; +import ownradio.service.DeviceService; + +import java.util.UUID; + +@Service +public class DeviceServiceImpl implements DeviceService { + + private final DeviceRepository deviceRepository; + + @Autowired + public DeviceServiceImpl(DeviceRepository deviceRepository) { + this.deviceRepository = deviceRepository; + } + + + @Override + public void save(Device device) { + deviceRepository.saveAndFlush(device); + } + + @Override + public Device getById(UUID uuid) { + return deviceRepository.findOne(uuid); + } +} diff --git a/v3/src/main/java/ownradio/service/impl/DownloadTrackServiceImpl.java b/v3/src/main/java/ownradio/service/impl/DownloadTrackServiceImpl.java new file mode 100644 index 0000000..0257de2 --- /dev/null +++ b/v3/src/main/java/ownradio/service/impl/DownloadTrackServiceImpl.java @@ -0,0 +1,24 @@ +package ownradio.service.impl; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import ownradio.domain.DownloadTrack; +import ownradio.repository.DownloadTrackRepository; +import ownradio.service.DownloadTrackService; + +@Service +public class DownloadTrackServiceImpl implements DownloadTrackService { + + private final DownloadTrackRepository downloadTrackRepository; + + @Autowired + public DownloadTrackServiceImpl(DownloadTrackRepository downloadTrackRepository) { + this.downloadTrackRepository = downloadTrackRepository; + } + + + @Override + public void save(DownloadTrack downloadTrack) { + downloadTrackRepository.saveAndFlush(downloadTrack); + } +} diff --git a/v3/src/main/java/ownradio/service/impl/HistoryServiceImpl.java b/v3/src/main/java/ownradio/service/impl/HistoryServiceImpl.java new file mode 100644 index 0000000..cf40353 --- /dev/null +++ b/v3/src/main/java/ownradio/service/impl/HistoryServiceImpl.java @@ -0,0 +1,45 @@ +package ownradio.service.impl; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import ownradio.domain.History; +import ownradio.domain.Rating; +import ownradio.repository.HistoryRepository; +import ownradio.repository.RatingRepository; +import ownradio.service.HistoryService; + +@Service +public class HistoryServiceImpl implements HistoryService { + + private final HistoryRepository historyRepository; + private final RatingRepository ratingRepository; + + @Autowired + public HistoryServiceImpl(HistoryRepository historyRepository, RatingRepository ratingRepository) { + this.historyRepository = historyRepository; + this.ratingRepository = ratingRepository; + } + + @Transactional + @Override + public void save(History history) { + historyRepository.saveAndFlush(history); + + Rating rating = ratingRepository.findByUserAndTrack(history.getDevice().getUser(), history.getTrack()); + if(rating != null) { + int ratingsum = rating.getRatingsum() + history.getIsListen(); + rating.setLastlisten(history.getLastListen()); + rating.setRatingsum(ratingsum); + ratingRepository.saveAndFlush(rating); + } + else { + rating = new Rating(); + rating.setUser(history.getDevice().getUser()); + rating.setTrack(history.getTrack()); + rating.setLastlisten(history.getLastListen()); + rating.setRatingsum(history.getIsListen()); + ratingRepository.saveAndFlush(rating); + } + } +} diff --git a/v3/src/main/java/ownradio/service/impl/RatingServiceImpl.java b/v3/src/main/java/ownradio/service/impl/RatingServiceImpl.java new file mode 100644 index 0000000..2abce5f --- /dev/null +++ b/v3/src/main/java/ownradio/service/impl/RatingServiceImpl.java @@ -0,0 +1,24 @@ +package ownradio.service.impl; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import ownradio.domain.Rating; +import ownradio.repository.RatingRepository; +import ownradio.service.RatingService; + +@Service +public class RatingServiceImpl implements RatingService { + + private final RatingRepository ratingRepository; + + @Autowired + public RatingServiceImpl(RatingRepository ratingRepository) { + this.ratingRepository = ratingRepository; + } + + + @Override + public void save(Rating rating) { + ratingRepository.saveAndFlush(rating); + } +} diff --git a/v3/src/main/java/ownradio/service/impl/TrackServiceImpl.java b/v3/src/main/java/ownradio/service/impl/TrackServiceImpl.java new file mode 100644 index 0000000..b9b259f --- /dev/null +++ b/v3/src/main/java/ownradio/service/impl/TrackServiceImpl.java @@ -0,0 +1,127 @@ +package ownradio.service.impl; + +import com.mpatric.mp3agic.ID3v1; +import com.mpatric.mp3agic.ID3v2; +import com.mpatric.mp3agic.Mp3File; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.StringUtils; +import org.springframework.web.multipart.MultipartFile; +import ownradio.domain.NextTrack; +import ownradio.domain.Track; +import ownradio.repository.TrackRepository; +import ownradio.service.TrackService; +import ownradio.util.ResourceUtil; + +import java.io.File; +import java.util.List; +import java.util.UUID; + +@Service +public class TrackServiceImpl implements TrackService { + + private final TrackRepository trackRepository; + + @Autowired + public TrackServiceImpl(TrackRepository trackRepository) { + this.trackRepository = trackRepository; + } + + @Override + @Transactional(readOnly = true) + public Track getById(UUID id) { + return trackRepository.findOne(id); + } + + @Override + @Transactional + public UUID getNextTrackId(UUID deviceId) { + return trackRepository.getNextTrackId(deviceId); + } + + @Override + @Transactional + public NextTrack getNextTrackIdV2(UUID deviceId) { + NextTrack nextTrack = new NextTrack(); + List objects = trackRepository.getNextTrackV2(deviceId); + try{ + if(objects != null) { + nextTrack.setTrackid(UUID.fromString((String) objects.get(0)[0])); + nextTrack.setMethodid((Integer) objects.get(0)[1]); + return nextTrack; + }else{ + return null; + } + }catch (Exception ex){ + return null; + } + } + + @Override + @Transactional + public void save(Track track, MultipartFile file) { + boolean result = trackRepository.registerTrack(track.getRecid(), track.getLocaldevicepathupload(), track.getPath(), track.getDevice().getRecid()); + if (!result) { + throw new RuntimeException(); + } + + Track storeTrack = trackRepository.findOne(track.getRecid()); + + String dirName = storeTrack.getDevice().getUser().getRecid().toString(); + String fileName = storeTrack.getRecid() + "." + StringUtils.getFilenameExtension(file.getOriginalFilename()); + String filePath = ResourceUtil.save(dirName, fileName, file); + + storeTrack.setPath(filePath); + } + + @Override + @Transactional + public void setTrackInfo(UUID trackid) { + String artist = null; + String title = null; + boolean artistFlag = false; + boolean titleFlag = false; + + byte[] buf; + + if (trackid != null) { + try { + Track track = trackRepository.findOne(trackid); + Mp3File mp3File = new Mp3File(track.getPath()); + track.setLength((int) mp3File.getLengthInSeconds());//duration track + track.setSize((int) mp3File.getLength() / 1024);//size in kilobytes + + if (mp3File.hasId3v1Tag()) { + ID3v1 id3v1Tag1 = mp3File.getId3v1Tag(); + title = new String(id3v1Tag1.getTitle().getBytes("UTF16"),"Cp1251").replaceAll("\u0000", "").substring(2); +// title = id3v1Tag1.getTitle(); + artist = new String(id3v1Tag1.getArtist().getBytes("UTF16"),"Cp1251").replaceAll("\u0000", "").substring(2); +// artist = id3v1Tag1.getArtist(); + }else if (mp3File.hasId3v2Tag()) { + ID3v2 id3v2Tag2 = mp3File.getId3v2Tag(); + title = id3v2Tag2.getTitle(); + title = title.equals(id3v2Tag2.getTitle()) ? title : null; + artist = id3v2Tag2.getArtist(); + artist = artist.equals(id3v2Tag2.getArtist()) ? artist : null; + } + + if (title != null && !title.equals("null") && !title.isEmpty()) { + track.setRecname(title.replaceAll("\u0000", "")); + titleFlag = true; + } else + titleFlag = false; + if (artist != null && !artist.equals("null") && !artist.isEmpty()) { + track.setArtist(artist.replaceAll("\u0000", "")); + artistFlag = true; + } else + artistFlag = false; + + if (artistFlag && titleFlag) + track.setIsfilledinfo(1); + trackRepository.saveAndFlush(track); + } catch (Exception ex) { + } + } + } +} diff --git a/v3/src/main/java/ownradio/service/impl/UserServiceImpl.java b/v3/src/main/java/ownradio/service/impl/UserServiceImpl.java new file mode 100644 index 0000000..c5c88dc --- /dev/null +++ b/v3/src/main/java/ownradio/service/impl/UserServiceImpl.java @@ -0,0 +1,26 @@ +package ownradio.service.impl; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import ownradio.domain.User; +import ownradio.repository.UserRepository; +import ownradio.service.UserService; + +import java.util.UUID; + +@Service +public class UserServiceImpl implements UserService { + @Autowired + private UserRepository userRepository; + + @Override + public User getById(UUID id) { + return userRepository.findOne(id); + } + + @Override + public User save(User user) { + return userRepository.saveAndFlush(user); + } +} diff --git a/v3/src/main/java/ownradio/util/ReflectUtil.java b/v3/src/main/java/ownradio/util/ReflectUtil.java new file mode 100644 index 0000000..3e37036 --- /dev/null +++ b/v3/src/main/java/ownradio/util/ReflectUtil.java @@ -0,0 +1,51 @@ +package ownradio.util; + +import ownradio.annotation.DisplayName; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Field; +import java.util.*; + +/** + * Класс утилита для упрощения работы с рефлексией + * + * @author Alpenov Tanat + */ +public class ReflectUtil { + private ReflectUtil() { + } + + private static List getAllFields(List fields, Class type) { + fields.addAll(Arrays.asList(type.getDeclaredFields())); + + if (type.getSuperclass() != null) { + fields = getAllFields(fields, type.getSuperclass()); + } + + return fields; + } + + public static Map getDisplayNameFields(Object o) { + ResourceBundle res = ResourceUtil.getResourceBundle(); + + Map result = new HashMap<>(); + + getAllFields(new LinkedList<>(), o.getClass()).stream() + .filter(field -> field.isAnnotationPresent(DisplayName.class)) + .forEach(field -> { + for (Annotation annotation : field.getAnnotations()) { + if (annotation instanceof DisplayName) { + DisplayName displayName = (DisplayName) annotation; + + if (displayName.isVisible()) { + String key = displayName.key(); + result.put(key, res.getString(key)); + } + } + } + + }); + + return result; + } +} diff --git a/v3/src/main/java/ownradio/util/ResourceUtil.java b/v3/src/main/java/ownradio/util/ResourceUtil.java new file mode 100644 index 0000000..06ffb6d --- /dev/null +++ b/v3/src/main/java/ownradio/util/ResourceUtil.java @@ -0,0 +1,69 @@ +package ownradio.util; + +import org.springframework.util.ResourceUtils; +import org.springframework.web.multipart.MultipartFile; + +import java.io.File; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLClassLoader; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.Locale; +import java.util.ResourceBundle; + +/** + * Класс утилита для упрощения работы с ресурсами + * + * @author Alpenov Tanat + */ +public class ResourceUtil { + public static final String APP_DIR = System.getProperty("user.dir"); + public static final String UPLOAD_DIR = System.getProperty("upload.dir") == null ? APP_DIR + "/userfile/" : System.getProperty("upload.dir"); + public static final String MESSAGE_DIR = APP_DIR + "/msg"; + public static final String MESSAGE_BASE_NAME = "masseage"; + + private ResourceUtil() { + } + + public static byte[] read(String fileName) { + File file; + try { + file = ResourceUtils.getFile(fileName); + return Files.readAllBytes(file.toPath()); + } catch (IOException e) { + throw new RuntimeException("Error read resource"); + } + } + + public static String save(String userDir, String fileName, MultipartFile file) { + try { + File dir = new File(UPLOAD_DIR + userDir); + if (!dir.exists()) { + dir.mkdirs(); + } + + String filePath = Paths.get(UPLOAD_DIR, userDir, fileName).toString(); + file.transferTo(new File(filePath)); + + return filePath; + } catch (Exception e) { + throw new RuntimeException("Error save file", e); + } + } + + public static ResourceBundle getResourceBundle() { + File file = new File(MESSAGE_DIR); + URL[] urls; + try { + urls = new URL[]{file.toURI().toURL()}; + } catch (MalformedURLException e) { + throw new RuntimeException(e); + } + ClassLoader loader = new URLClassLoader(urls); + + return ResourceBundle.getBundle(MESSAGE_BASE_NAME, Locale.getDefault(), loader); + } + +} diff --git a/v3/src/main/java/ownradio/web/rest/v2/HistoryController.java b/v3/src/main/java/ownradio/web/rest/v2/HistoryController.java new file mode 100644 index 0000000..b0a000f --- /dev/null +++ b/v3/src/main/java/ownradio/web/rest/v2/HistoryController.java @@ -0,0 +1,69 @@ +package ownradio.web.rest.v2; + +import lombok.Data; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.format.annotation.DateTimeFormat; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.WebDataBinder; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; +import ownradio.domain.Device; +import ownradio.domain.History; +import ownradio.domain.Track; +import ownradio.domain.User; +import ownradio.service.DeviceService; +import ownradio.service.HistoryService; +import ownradio.service.TrackService; +import ownradio.service.UserService; + +import javax.persistence.Column; +import java.beans.PropertyEditorSupport; +import java.util.Date; +import java.util.UUID; + +/** + * WEB API для работы с историей прослушанных треков + * + * @author Alpenov Tanat + */ +@Slf4j +@RestController +@RequestMapping("/v2/histories") +public class HistoryController { + private final HistoryService historyService; + private final TrackService trackService; + private final DeviceService deviceService; + + @Autowired + public HistoryController(HistoryService historyService, TrackService trackService, DeviceService deviceService) { + this.historyService = historyService; + this.trackService = trackService; + this.deviceService = deviceService; + } + + @RequestMapping(value = "/{deviceId}/{trackId}", method = RequestMethod.POST) + public ResponseEntity save(@PathVariable UUID deviceId, @PathVariable UUID trackId, @RequestBody History history) { + try { + log.info("{} {}",deviceId.toString(),trackId.toString()); + log.info("{} {} {}",history.getLastListen(), history.getIsListen(), history.getMethod()); + Track track = trackService.getById(trackId); + Device device = deviceService.getById(deviceId); + + if (track == null || device == null) { + throw new RuntimeException("Track or Device is null"); + } + + history.setTrack(track); + history.setDevice(device); + + historyService.save(history); + + return new ResponseEntity(history, HttpStatus.OK); + } catch (Exception e) { + return new ResponseEntity(history, HttpStatus.INTERNAL_SERVER_ERROR); + } + } + +} diff --git a/v3/src/main/java/ownradio/web/rest/v2/TrackController.java b/v3/src/main/java/ownradio/web/rest/v2/TrackController.java new file mode 100644 index 0000000..1dbd30f --- /dev/null +++ b/v3/src/main/java/ownradio/web/rest/v2/TrackController.java @@ -0,0 +1,102 @@ +package ownradio.web.rest.v2; + +import lombok.Data; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; +import ownradio.domain.Device; +import ownradio.domain.Track; +import ownradio.service.TrackService; +import ownradio.util.ResourceUtil; + +import java.util.UUID; + +/** + * WEB API для работы с треками + * + * @author Alpenov Tanat + */ +@Slf4j +@RestController +@RequestMapping(value = "/v2/tracks") +public class TrackController { + + private final TrackService trackService; + + @Autowired + public TrackController(TrackService trackService) { + this.trackService = trackService; + } + + @Data + private static class TrackDTO { + private UUID fileGuid; + private String fileName; + private String filePath; + private UUID deviceId; + private MultipartFile musicFile; + + public Track getTrack() { + Device device = new Device(); + device.setRecid(deviceId); + + Track track = new Track(); + track.setRecid(fileGuid); + track.setRecname(fileName); + track.setDevice(device); + track.setPath("---"); + track.setLocaldevicepathupload(filePath); + + return track; + } + } + + @RequestMapping(method = RequestMethod.POST) + public ResponseEntity save(TrackDTO trackDTO) { + if (trackDTO.getMusicFile().isEmpty()) { + return new ResponseEntity(HttpStatus.BAD_REQUEST); + } + + try { + trackService.save(trackDTO.getTrack(), trackDTO.getMusicFile()); + + return new ResponseEntity(HttpStatus.CREATED); + } catch (Exception e) { + return new ResponseEntity(HttpStatus.INTERNAL_SERVER_ERROR); + } + + } + + @RequestMapping(value = "/{id}", method = RequestMethod.GET) + public ResponseEntity getTrack(@PathVariable UUID id) { + Track track = trackService.getById(id); + + if (track != null) { + byte[] bytes = ResourceUtil.read(track.getPath()); + return new ResponseEntity<>(bytes, getHttpAudioHeaders(), HttpStatus.OK); + } else { + return new ResponseEntity<>(HttpStatus.NOT_FOUND); + } + } + + @RequestMapping(value = "/{deviceId}/next", method = RequestMethod.GET) + public ResponseEntity getNextTrackId(@PathVariable UUID deviceId) { + UUID trackId = trackService.getNextTrackId(deviceId); + + if (trackId != null) { + return new ResponseEntity<>(trackId, HttpStatus.OK); + } else { + return new ResponseEntity<>(HttpStatus.NOT_FOUND); + } + } + + private HttpHeaders getHttpAudioHeaders() { + HttpHeaders responseHeaders = new HttpHeaders(); + responseHeaders.add("Content-Type", "audio/mpeg"); + return responseHeaders; + } +} diff --git a/src/src/main/java/ownradio/web/rest/v3/HistoryController.java b/v3/src/main/java/ownradio/web/rest/v3/HistoryController.java similarity index 100% rename from src/src/main/java/ownradio/web/rest/v3/HistoryController.java rename to v3/src/main/java/ownradio/web/rest/v3/HistoryController.java diff --git a/src/src/main/java/ownradio/web/rest/v3/TrackController.java b/v3/src/main/java/ownradio/web/rest/v3/TrackController.java similarity index 100% rename from src/src/main/java/ownradio/web/rest/v3/TrackController.java rename to v3/src/main/java/ownradio/web/rest/v3/TrackController.java diff --git a/v3/src/main/resources/application-dev.properties b/v3/src/main/resources/application-dev.properties new file mode 100644 index 0000000..5814481 --- /dev/null +++ b/v3/src/main/resources/application-dev.properties @@ -0,0 +1,17 @@ +this-url:http://${server.address:localhost}:${server.port:8080} +spring.h2.console.enabled=true +spring.datasource.url=jdbc:h2:mem:.;MODE=PostgreSQL +spring.datasource.username=sa +spring.datasource.password= +spring.datasource.platform=org.hibernate.dialect.H2Dialect +spring.http.multipart.max-file-size=256Mb +spring.http.multipart.max-request-size=256Mb +#spring.jackson.property-naming-strategy=CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES +spring.jpa.properties.hibernate.format_sql=true +spring.jpa.show-sql=true +spring.jpa.hibernate.ddl-auto=update +logging.file=logs/app.log +logging.level.ownradio=debug +logging.level.org.springframework=warn +logging.level.org.hibernate=warn + diff --git a/v3/src/main/resources/application-prod.properties b/v3/src/main/resources/application-prod.properties new file mode 100644 index 0000000..ef6b7df --- /dev/null +++ b/v3/src/main/resources/application-prod.properties @@ -0,0 +1,25 @@ +#spring.datasource.url=jdbc:postgresql://127.0.0.1:5432/ownradio +#spring.datasource.username=tanat +#spring.datasource.password=123456 +spring.datasource.url=jdbc:postgresql://10.10.0.67:5432/ownRadioJavaTest +spring.datasource.username=a.polunina +spring.datasource.password=6ImtqMGAt1 +spring.datasource.platform=org.hibernate.dialect.PostgreSQL94Dialect +spring.datasource.test-while-idle=true +spring.datasource.test-on-borrow=true +spring.datasource.validation-query=SELECT 1 +spring.datasource.time-between-eviction-runs-millis=5000 +spring.datasource.min-evictable-idle-time-millis=60000 +spring.http.multipart.max-file-size=256Mb +spring.http.multipart.max-request-size=256Mb +#spring.jackson.property-naming-strategy=CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES +spring.jpa.properties.hibernate.format_sql=true +spring.jpa.show-sql=true +spring.jpa.hibernate.ddl-auto=update +logging.file=logs/app.log +logging.level.ownradio=info +logging.level.org.springframework=warn +logging.level.org.hibernate=warn +#server.port = 9090 +spring.datasource.schema=classpath:/data/postgresql/schema.sql +#spring.datasource.data=classpath:/data/postgresql/data.sql \ No newline at end of file diff --git a/v3/src/main/resources/data/h2/schema.sql b/v3/src/main/resources/data/h2/schema.sql new file mode 100644 index 0000000..e69de29 diff --git a/v3/src/main/resources/data/postgresql/schema.sql b/v3/src/main/resources/data/postgresql/schema.sql new file mode 100644 index 0000000..6806721 --- /dev/null +++ b/v3/src/main/resources/data/postgresql/schema.sql @@ -0,0 +1,305 @@ +CREATE OR REPLACE FUNCTION getnexttrackid(IN i_deviceid UUID) + RETURNS SETOF UUID AS +' +DECLARE + i_userid uuid = i_deviceid; + BEGIN + -- Добавляем устройство, если его еще не существует + -- Если ID устройства еще нет в БД + IF NOT EXISTS(SELECT recid + FROM devices + WHERE recid = i_deviceid) + THEN + + -- Добавляем нового пользователя + INSERT INTO users (recid, recname, reccreated) SELECT + i_userid, + ''New user recname'', + now(); + + -- Добавляем новое устройство + INSERT INTO devices (recid, userid, recname, reccreated) SELECT + i_deviceid, + i_userid, + ''New device recname'', + now(); + ELSE + SELECT (SELECT userid + FROM devices + WHERE recid = i_deviceid + LIMIT 1) + INTO i_userid; + END IF; + + RETURN QUERY + SELECT tracks.recid + FROM tracks + LEFT JOIN + ratings + ON tracks.recid = ratings.trackid AND ratings.userid = i_userid + WHERE ratings.ratingsum >=0 OR ratings.ratingsum is null + ORDER BY RANDOM() + LIMIT 1; +END; +' +LANGUAGE plpgsql; + + +CREATE OR REPLACE FUNCTION getnexttrackid_string(i_deviceid UUID) + RETURNS SETOF CHARACTER VARYING AS +' +BEGIN + RETURN QUERY SELECT CAST(getnexttrackid(i_deviceid) AS CHARACTER VARYING); +END; +' +LANGUAGE plpgsql; + + +CREATE OR REPLACE FUNCTION registertrack( + i_trackid UUID, + i_localdevicepathupload CHARACTER VARYING, + i_path CHARACTER VARYING, + i_deviceid UUID) + RETURNS BOOLEAN AS +' +DECLARE + i_userid UUID = i_deviceid; + i_historyid UUID; + i_ratingid UUID; +BEGIN + CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; + SELECT uuid_generate_v4() + INTO i_historyid; + SELECT uuid_generate_v4() + INTO i_ratingid; + + -- + -- Функция добавляет запись о треке в таблицу треков и делает сопутствующие записи в + -- таблицу статистики прослушивания и рейтингов. Если пользователя, загружающего трек + -- нет в базе, то он добавляется в таблицу пользователей. + -- + + -- Добавляем устройство, если его еще не существует + -- Если ID устройства еще нет в БД + IF NOT EXISTS(SELECT recid + FROM devices + WHERE recid = i_deviceid) + THEN + + -- Добавляем нового пользователя + INSERT INTO users (recid, recname, reccreated) SELECT + i_userid, + ''New user recname'', + now(); + + -- Добавляем новое устройство + INSERT INTO devices (recid, userid, recname, reccreated) SELECT + i_deviceid, + i_userid, + ''New device recname'', + now(); + ELSE + SELECT (SELECT userid + FROM devices + WHERE recid = i_deviceid + LIMIT 1) + INTO i_userid; + END IF; + + -- Добавляем трек в базу данных + INSERT INTO tracks (recid, localdevicepathupload, path, deviceid, reccreated, isexist) + VALUES (i_trackid, i_localdevicepathupload, i_path, i_deviceid, now(), 1); + + -- Добавляем запись о прослушивании трека в таблицу истории прослушивания + INSERT INTO histories (recid, deviceid, trackid, isListen, lastListen, methodid, reccreated) + VALUES (i_historyid, i_deviceid, i_trackid, 1, now(), 2, now()); + + -- Добавляем запись в таблицу рейтингов + INSERT INTO ratings (recid, userid, trackid, lastListen, ratingsum, reccreated) + VALUES (i_ratingid, i_userid, i_trackid, now(), 1, now()); + + RETURN TRUE; +END; +' +LANGUAGE plpgsql; + + +CREATE OR REPLACE FUNCTION getnexttrackid_v2(i_deviceid uuid) + RETURNS TABLE(track uuid, methodid integer) +AS +' +DECLARE + i_userid uuid = i_deviceid; + rnd integer = (select trunc(random() * 10)); -- получаем случайное число от 0 до 9 + o_methodid integer; -- id метода выбора трека +BEGIN + + -- Выбираем следующий трек + + -- В 9/10 случаях выбираем трек из треков пользователя (добавленных им или прослушанных до конца) + -- с положительным рейтингом, за исключением прослушанных за последние сутки + IF (rnd > 1) + THEN + o_methodid = 2; + RETURN QUERY + SELECT trackid, o_methodid + FROM ratings + WHERE userid = i_userid + AND lastlisten < localtimestamp - interval ''1 day'' + AND ratingsum >= 0 + AND (SELECT isexist FROM tracks WHERE recid = trackid) = 1 + ORDER BY RANDOM() + LIMIT 1; + + -- Если такой трек найден - выход из функции, возврат найденного значения + IF FOUND + THEN RETURN; + END IF; + END IF; + + -- В 1/10 случае выбираем случайный трек из ни разу не прослушанных пользователем треков + o_methodid = 3; + RETURN QUERY + SELECT recid, o_methodid + FROM tracks + WHERE recid NOT IN + (SELECT trackid + FROM ratings + WHERE userid = i_userid) + AND isexist = 1 + ORDER BY RANDOM() + LIMIT 1; + + -- Если такой трек найден - выход из функции, возврат найденного значения + IF FOUND + THEN RETURN; + END IF; + + -- Если предыдущие запросы вернули null, выбираем случайный трек + o_methodid = 1; + RETURN QUERY + SELECT recid, o_methodid + FROM tracks + WHERE isexist = 1 + ORDER BY RANDOM() + LIMIT 1; + RETURN; +END; +' +LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION getnexttrackid_v3(IN i_deviceid uuid) + RETURNS TABLE(track uuid, methodid integer) AS +' +DECLARE + i_userid uuid = i_deviceid; + rnd integer = (select trunc(random() * 1001)); + o_methodid integer; -- id метода выбора трека + owntracks integer; -- количество "своих" треков пользователя (обрезаем на 900 шт) +BEGIN + -- Выбираем следующий трек + + -- Определяем количество "своих" треков пользователя, ограничивая его 900 + owntracks = (SELECT COUNT(*) FROM ( + SELECT * FROM ratings + WHERE userid = i_userid + AND ratingsum >=0 + LIMIT 900) AS count) ; + + -- Если rnd меньше количества "своих" треков, выбираем трек из треков пользователя (добавленных им или прослушанных до конца) + -- с положительным рейтингом, за исключением прослушанных за последние сутки + + IF (rnd < owntracks) + THEN + o_methodid = 2; -- метод выбора из своих треков + RETURN QUERY + SELECT trackid, o_methodid + FROM ratings + WHERE userid = i_userid + AND lastlisten < localtimestamp - interval ''1 day'' + AND ratingsum >= 0 + AND (SELECT isexist FROM tracks WHERE recid = trackid) = 1 + ORDER BY RANDOM() + LIMIT 1; + + -- Если такой трек найден - выход из функции, возврат найденного значения + IF FOUND + THEN RETURN; + END IF; + END IF; + + -- В 1/10 случае выбираем случайный трек из ни разу не прослушанных пользователем треков + o_methodid = 3; -- метод выбора из непрослушанных треков + RETURN QUERY + SELECT recid, o_methodid + FROM tracks + WHERE recid NOT IN + (SELECT trackid + FROM ratings + WHERE userid = i_userid) + AND isexist = 1 + ORDER BY RANDOM() + LIMIT 1; + + -- Если такой трек найден - выход из функции, возврат найденного значения + IF FOUND + THEN RETURN; + END IF; + + -- Если предыдущие запросы вернули null, выбираем случайный трек + o_methodid = 1; -- метод выбора случайного трека + RETURN QUERY + SELECT recid, o_methodid + FROM tracks + WHERE isexist = 1 + ORDER BY RANDOM() + LIMIT 1; + RETURN; +END; +' +LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION getnexttrack(i_deviceid UUID) + RETURNS TABLE( + track character varying + , methodid integer) + AS +' +DECLARE + i_userid uuid = i_deviceid; -- в дальнейшем заменить получением userid по deviceid +BEGIN + -- Добавляем устройство, если его еще не существует + -- Если ID устройства еще нет в БД + IF NOT EXISTS(SELECT recid + FROM devices + WHERE recid = i_deviceid) + THEN + + -- Добавляем нового пользователя + INSERT INTO users (recid, recname, reccreated) SELECT + i_userid, + ''New user recname'', + now(); + + -- Добавляем новое устройство + INSERT INTO devices (recid, userid, recname, reccreated) SELECT + i_deviceid, + i_userid, + ''New device recname'', + now(); + ELSE + SELECT (SELECT userid + FROM devices + WHERE recid = i_deviceid + LIMIT 1) + INTO i_userid; + END IF; + + -- Возвращаем trackid, конвертируя его в character varying и methodid + RETURN QUERY SELECT + CAST((nexttrack.track) AS CHARACTER VARYING), + nexttrack.methodid + FROM getnexttrackid_v3(i_deviceid) AS nexttrack; +END; +' +LANGUAGE plpgsql; \ No newline at end of file diff --git a/v3/src/test/java/ownradio/ApplicationTests.java b/v3/src/test/java/ownradio/ApplicationTests.java new file mode 100644 index 0000000..62ef2c1 --- /dev/null +++ b/v3/src/test/java/ownradio/ApplicationTests.java @@ -0,0 +1,18 @@ +package ownradio; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.junit4.SpringRunner; + +@ActiveProfiles("dev") +@RunWith(SpringRunner.class) +@SpringBootTest +public class ApplicationTests { + + @Test + public void contextLoads() { + } + +} diff --git a/v3/src/test/java/ownradio/domain/UserTest.java b/v3/src/test/java/ownradio/domain/UserTest.java new file mode 100644 index 0000000..c8d24be --- /dev/null +++ b/v3/src/test/java/ownradio/domain/UserTest.java @@ -0,0 +1,44 @@ +package ownradio.domain; + +import org.junit.Before; +import org.junit.Test; + +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; + +import static org.hamcrest.Matchers.equalTo; +import static org.junit.Assert.assertThat; +import static ownradio.util.ReflectUtil.getDisplayNameFields; + +public class UserTest { + + private Map expected; + + @Before + public void setUp() throws Exception { + expected = new HashMap<>(); + } + + @Test + public void showDisplayNameRu() throws Exception { + Locale.setDefault(new Locale("ru")); + + expected.put("id", "Название идентификатора на ru"); + + Map actual = getDisplayNameFields(new User()); + + assertThat(actual, equalTo(expected)); + } + + @Test + public void showDisplayNameEn() throws Exception { + Locale.setDefault(new Locale("en")); + + expected.put("id", "Название идентификатора на en"); + + Map actual = getDisplayNameFields(new User()); + + assertThat(actual, equalTo(expected)); + } +} \ No newline at end of file diff --git a/v3/src/test/java/ownradio/repository/HistoryRepositoryTest.java b/v3/src/test/java/ownradio/repository/HistoryRepositoryTest.java new file mode 100644 index 0000000..4880970 --- /dev/null +++ b/v3/src/test/java/ownradio/repository/HistoryRepositoryTest.java @@ -0,0 +1,68 @@ +package ownradio.repository; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager; +import org.springframework.test.context.junit4.SpringRunner; +import ownradio.domain.Device; +import ownradio.domain.History; +import ownradio.domain.Track; +import ownradio.domain.User; + +import java.util.Calendar; +import java.util.Date; + +import static org.hamcrest.CoreMatchers.nullValue; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; +import static org.junit.Assert.assertThat; + +@RunWith(SpringRunner.class) +@DataJpaTest +public class HistoryRepositoryTest { + @Autowired + private HistoryRepository historyRepository; + + @Autowired + private TestEntityManager entityManager; + + private History history; + + @Before + public void setUp() throws Exception { + User user = entityManager.persist(new User()); + Device device = entityManager.persist(new Device(user, "1")); + Track track = entityManager.persist(new Track("1", device, "1", 0, "", 0, null, null, null, 1)); + + history = new History(track, Calendar.getInstance(), 0, "post", 1, device); + entityManager.persist(history); + } + + @Test + public void createdAt() throws Exception { + assertThat(history.getReccreated(), not(nullValue())); + assertThat(history.getReccreated().getTime().toString(), is(Calendar.getInstance().getTime().toString())); + } + + @Test + public void updatedAt() throws Exception { + assertThat(history.getReccreated(), not(nullValue())); + assertThat(history.getReccreated().getTime().toString(), is(Calendar.getInstance().getTime().toString())); + assertThat(history.getRecupdated(), is(nullValue())); + + History storeHistory = historyRepository.findOne(history.getRecid()); + storeHistory.setIsListen(1); + historyRepository.saveAndFlush(storeHistory); + + assertThat(storeHistory.getReccreated(), not(nullValue())); + assertThat(storeHistory.getReccreated().getTime().toString(), is(history.getReccreated().getTime().toString())); + + assertThat(storeHistory.getRecupdated(), not(nullValue())); + assertThat(storeHistory.getRecupdated().getTime().toString(), is(Calendar.getInstance().getTime().toString())); + } + + +} \ No newline at end of file diff --git a/v3/src/test/java/ownradio/repository/RatingRepositoryTest.java b/v3/src/test/java/ownradio/repository/RatingRepositoryTest.java new file mode 100644 index 0000000..3930678 --- /dev/null +++ b/v3/src/test/java/ownradio/repository/RatingRepositoryTest.java @@ -0,0 +1,45 @@ +package ownradio.repository; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.junit4.SpringRunner; +import ownradio.domain.Rating; +import ownradio.domain.Track; +import ownradio.domain.User; + +import java.util.UUID; + +import static org.junit.Assert.*; + +/** + * Created by a.polunina on 10.11.2016. + */ +@ActiveProfiles("prod") +@RunWith(SpringRunner.class) +@SpringBootTest +//@DataJpaTest +public class RatingRepositoryTest { + @Autowired + private RatingRepository ratingRepository; + @Test + public void findByUser() throws Exception { + User user = new User(); + user.setRecid(UUID.fromString("bfa8137b-c917-4496-8fe7-39202322d257")); + Rating rating = ratingRepository.findByUser(user); + System.out.println(rating); + } + + @Test + public void findByUserAndTrack() throws Exception { + User user = new User(); + Track track = new Track(); + user.setRecid(UUID.fromString("bfa8137b-c917-4496-8fe7-39202322d257")); + track.setRecid(UUID.fromString("bfa8137b-c917-4496-8fe7-39202322d257")); + Rating rating = ratingRepository.findByUserAndTrack(user, track); + System.out.println(rating); + } +} \ No newline at end of file diff --git a/v3/src/test/java/ownradio/repository/TrackRepositoryTest.java b/v3/src/test/java/ownradio/repository/TrackRepositoryTest.java new file mode 100644 index 0000000..b4fdc63 --- /dev/null +++ b/v3/src/test/java/ownradio/repository/TrackRepositoryTest.java @@ -0,0 +1,57 @@ +package ownradio.repository; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.orm.jpa.AutoConfigureTestDatabase; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.junit4.SpringRunner; +import ownradio.domain.Device; +import ownradio.domain.Track; +import ownradio.domain.User; + +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; + +import static org.junit.Assert.assertTrue; + +@ActiveProfiles("prod") +@RunWith(SpringRunner.class) +@DataJpaTest +@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) +public class TrackRepositoryTest { + @Autowired + private TrackRepository trackRepository; + + @Autowired + private TestEntityManager entityManager; + + private User user; + private Device device; + + @Before + public void setUp() throws Exception { + user = entityManager.persist(new User()); + device = entityManager.persist(new Device(user, "123")); + entityManager.persist(new Track("1", device, "1", 0, "", 0, null, null, null, 1)); + entityManager.persist(new Track("2", device, "1", 0, "", 0, null, null, null, 1)); + entityManager.persist(new Track("4", device, "1", 0, "", 0, null, null, null, 1)); + } + + @Test + public void getNextTrackId() throws Exception { + Set trackSet = new HashSet<>(); + + for (int i = 0; i < 3; i++) { + UUID track = trackRepository.getNextTrackId(device.getRecid()); + trackSet.add(track); + } + + assertTrue(trackSet.size() > 1); + } + +} \ No newline at end of file diff --git a/v3/src/test/java/ownradio/service/TrackServiceTest.java b/v3/src/test/java/ownradio/service/TrackServiceTest.java new file mode 100644 index 0000000..abe1f81 --- /dev/null +++ b/v3/src/test/java/ownradio/service/TrackServiceTest.java @@ -0,0 +1,76 @@ +package ownradio.service; + +import org.apache.tomcat.util.http.fileupload.FileUtils; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.springframework.mock.web.MockMultipartFile; +import org.springframework.test.context.junit4.SpringRunner; +import ownradio.domain.Device; +import ownradio.domain.Track; +import ownradio.domain.User; +import ownradio.repository.TrackRepository; +import ownradio.service.impl.TrackServiceImpl; + +import java.io.File; +import java.util.UUID; + +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; +import static org.mockito.BDDMockito.given; +import static ownradio.util.ResourceUtil.UPLOAD_DIR; + +@RunWith(SpringRunner.class) +public class TrackServiceTest { + @Mock + private TrackRepository trackRepository; + + protected TrackService trackService; + + private UUID userId = UUID.randomUUID(); + private UUID trackId = UUID.randomUUID(); + private UUID deviceId = UUID.randomUUID(); + private Track expected; + + @Before + public void setUp() throws Exception { + trackService = new TrackServiceImpl(trackRepository); + expected = new Track(); + expected.setRecid(trackId); + + User user = new User(); + user.setRecid(userId); + + Device device = new Device(user, "123"); + device.setRecid(deviceId); + expected.setDevice(device); + } + + @After + public void tearDown() throws Exception { + FileUtils.deleteDirectory(new File(UPLOAD_DIR)); + } + + @Test + public void getNextTrackId() throws Exception { + given(this.trackRepository.getNextTrackId(trackId)).willReturn(trackId); + + UUID actual = trackService.getNextTrackId(trackId); + + assertThat(actual, equalTo(expected.getRecid())); + } + + @Test + public void save() throws Exception { + MockMultipartFile correctFile = new MockMultipartFile("file", "test.mp3", "text/plain", "Text".getBytes()); + + given(this.trackRepository.registerTrack(expected.getRecid(), expected.getLocaldevicepathupload(), expected.getPath(), expected.getDevice().getRecid())).willReturn(true); + given(this.trackRepository.findOne(expected.getRecid())).willReturn(expected); + trackService.save(expected, correctFile); + + assertThat(new File(expected.getPath()).exists(), is(true)); + } +} \ No newline at end of file diff --git a/v3/src/test/java/ownradio/web/rest/v2/HistoryControllerTest.java b/v3/src/test/java/ownradio/web/rest/v2/HistoryControllerTest.java new file mode 100644 index 0000000..be5ae70 --- /dev/null +++ b/v3/src/test/java/ownradio/web/rest/v2/HistoryControllerTest.java @@ -0,0 +1,118 @@ +package ownradio.web.rest.v2; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.json.JSONObject; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.web.servlet.MockMvc; +import ownradio.domain.Device; +import ownradio.domain.History; +import ownradio.domain.Track; +import ownradio.domain.User; +import ownradio.service.DeviceService; +import ownradio.service.HistoryService; +import ownradio.service.TrackService; +import ownradio.service.UserService; + +import java.util.UUID; + +import static org.mockito.BDDMockito.given; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.doThrow; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@RunWith(SpringRunner.class) +@WebMvcTest(HistoryController.class) +public class HistoryControllerTest { + public static final UUID TRACK_UUID = UUID.randomUUID(); + public static final UUID USER_UUID = UUID.randomUUID(); + public static final UUID DEVICE_UUID = UUID.randomUUID(); + + @MockBean + private HistoryService historyService; + + @MockBean + private UserService userService; + + @MockBean + private TrackService trackService; + + @MockBean + private DeviceService deviceService; + + @Autowired + private MockMvc mockMvc; + + private ObjectMapper mapper = new ObjectMapper(); + + private User user; + private Track track; + private Device device; + + @Before + public void setUp() throws Exception { + user = new User(); + track = new Track(); + device = new Device(); + } + + @Test + public void saveStatusIsOk() throws Exception { + given(this.userService.getById(USER_UUID)).willReturn(user); + given(this.trackService.getById(TRACK_UUID)).willReturn(track); + given(this.deviceService.getById(DEVICE_UUID)).willReturn(device); + + JSONObject obj = new JSONObject(); + obj.put("lastListen", "2016-11-28T12:34:56"); + obj.put("isListen", "1"); + obj.put("method", "method"); + + mockMvc.perform(post("/v2/histories/{deviceId}/{trackId}", DEVICE_UUID, TRACK_UUID) + .contentType(MediaType.APPLICATION_JSON) + .content(obj.toString()) +// .param("lastListen", "2016-11-28 12:34:56") +// .param("isListen", "1") +// .param("method", "method") + ) + .andDo(print()) + .andExpect( + status().isOk() + ); + } + + @Test + public void saveStatusIsInternalServerError() throws Exception { + given(this.userService.getById(USER_UUID)).willReturn(user); + given(this.trackService.getById(TRACK_UUID)).willReturn(track); + given(this.deviceService.getById(DEVICE_UUID)).willReturn(device); + + doThrow(RuntimeException.class).when(this.historyService).save(any(History.class)); + + JSONObject obj = new JSONObject(); + obj.put("lastListen", "2016-11-28T12:34:56"); + obj.put("isListen", "1"); + obj.put("method", "method"); + + mockMvc.perform(post("/v2/histories/{deviceId}/{trackId}", DEVICE_UUID, TRACK_UUID) + .contentType(MediaType.APPLICATION_JSON) + .content(obj.toString()) +// .param("lastListen", "2016-11-28 12:34:56") +// .param("isListen", "1") +// .param("method", "method") + ) + .andDo(print()) + .andExpect( + status().isInternalServerError() + ); + } + + +} \ No newline at end of file diff --git a/v3/src/test/java/ownradio/web/rest/v2/TrackControllerTest.java b/v3/src/test/java/ownradio/web/rest/v2/TrackControllerTest.java new file mode 100644 index 0000000..b36bcb6 --- /dev/null +++ b/v3/src/test/java/ownradio/web/rest/v2/TrackControllerTest.java @@ -0,0 +1,185 @@ +package ownradio.web.rest.v2; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.tomcat.util.http.fileupload.FileUtils; +import org.json.JSONObject; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; +import org.springframework.mock.web.MockMultipartFile; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.web.servlet.MockMvc; +import ownradio.domain.Device; +import ownradio.domain.Track; +import ownradio.domain.User; +import ownradio.service.DeviceService; +import ownradio.service.TrackService; +import ownradio.util.ResourceUtil; + +import java.io.File; +import java.util.UUID; + +import static org.hamcrest.core.Is.is; +import static org.mockito.BDDMockito.given; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.fileUpload; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static ownradio.util.ResourceUtil.UPLOAD_DIR; + +@RunWith(SpringRunner.class) +@WebMvcTest(TrackController.class) +public class TrackControllerTest { + public static final UUID TRACK_UUID = UUID.randomUUID(); + public static final UUID USER_UUID = UUID.randomUUID(); + public static final UUID DEVICE_UUID = UUID.randomUUID(); + public static final String FILE = TRACK_UUID + ".mp3"; + public static final String PATH = UPLOAD_DIR + USER_UUID + "/" + FILE; + + @MockBean + private TrackService trackService; + + @MockBean + private DeviceService deviceService; + + @Autowired + protected MockMvc mockMvc; + + private MockMultipartFile correctFile; + private MockMultipartFile emptyFile; + + private ObjectMapper mapper = new ObjectMapper(); + + private User user = new User(); + private Device device = new Device(); + private Track track; + + @Before + public void setUp() throws Exception { + user.setRecid(USER_UUID); + device.setRecid(DEVICE_UUID); + device.setUser(user); + + track = new Track(PATH, device, "---", 0, "", 0, null, null, null, 1); + + String requestParam = "musicFile"; + String originalFilename = "test.mp3"; + String contentType = "audio/mpeg"; + + correctFile = new MockMultipartFile(requestParam, originalFilename, contentType, "Text".getBytes()); + emptyFile = new MockMultipartFile(requestParam, originalFilename, contentType, "".getBytes()); + + ResourceUtil.save(USER_UUID.toString(), FILE, correctFile); + } + + @After + public void tearDown() throws Exception { + FileUtils.deleteDirectory(new File(UPLOAD_DIR)); + } + + @Test + public void saveStatusIsOk() throws Exception { + given(this.deviceService.getById(DEVICE_UUID)).willReturn(device); + + JSONObject obj = new JSONObject(); + obj.put("fileGuid", TRACK_UUID.toString()); + obj.put("fileName", correctFile.getOriginalFilename()); + obj.put("filePath", PATH); + obj.put("deviceId", DEVICE_UUID.toString()); + + mockMvc.perform(fileUpload("/v2/tracks") + .file(correctFile) + .accept(MediaType.APPLICATION_JSON_UTF8_VALUE) + .content(obj.toString()) + .contentType(MediaType.MULTIPART_FORM_DATA) +// .param("fileGuid", TRACK_UUID.toString()) +// .param("fileName", correctFile.getOriginalFilename()) +// .param("filePath", PATH) +// .param("deviceId", DEVICE_UUID.toString()) + ) + .andDo(print()) + .andExpect( + status().isCreated() + ); + } + + @Test + public void saveStatusIsBadRequest() throws Exception { + mockMvc.perform(fileUpload("/v2/tracks") + .file(emptyFile) + .accept(MediaType.APPLICATION_JSON_UTF8_VALUE) + .param("fileGuid", TRACK_UUID.toString()) + .param("fileName", correctFile.getOriginalFilename()) + .param("filePath", PATH) + .param("deviceId", DEVICE_UUID.toString()) + ) + .andDo(print()) + .andExpect( + status().isBadRequest() + ); + } + + @Test + public void getTrackStatusIsOk() throws Exception { + given(this.trackService.getById(TRACK_UUID)).willReturn(track); + + mockMvc.perform(get("/v2/tracks/{trackId}", TRACK_UUID).accept(MediaType.TEXT_PLAIN)) + .andDo(print()) + .andExpect( + status().isOk() + ) + .andExpect( + header().string("Content-Type", is("audio/mpeg")) + ) + .andExpect( + content().string("Text") + ); + } + + @Test + public void getTrackStatusIsNotFound() throws Exception { + + given(this.trackService.getById(TRACK_UUID)).willReturn(null); + + mockMvc.perform(get("/v2/tracks/{trackId}", TRACK_UUID).accept(MediaType.TEXT_PLAIN)) + .andDo(print()) + .andExpect( + status().isNotFound() + ); + } + + @Test + public void getNextTrackIdIsOk() throws Exception { + given(this.trackService.getNextTrackId(DEVICE_UUID)).willReturn(TRACK_UUID); + + mockMvc.perform(get("/v2/tracks/{deviceId}/next", DEVICE_UUID)) + .andDo(print()) + .andExpect( + status().isOk() + ) + .andExpect( + content().string(mapper.writeValueAsString(TRACK_UUID)) + ); + + } + + @Test + public void getNextTrackIdIsNotFound() throws Exception { + given(this.trackService.getNextTrackId(DEVICE_UUID)).willReturn(null); + + mockMvc.perform(get("/v2/tracks/{deviceId}/next", DEVICE_UUID)) + .andDo(print()) + .andExpect( + status().isNotFound() + ); + + } + +} \ No newline at end of file From 8570ea73d25048f64e69ebbb52c3b21b8a06b675 Mon Sep 17 00:00:00 2001 From: Alexandra Polunina Date: Tue, 17 Jan 2017 17:41:39 +0300 Subject: [PATCH 20/44] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8?= =?UTF-8?q?=D0=BB=D0=B0=20=D0=BF=D0=BE=D0=BB=D1=83=D1=87=D0=B5=D0=BD=D0=B8?= =?UTF-8?q?=D0=B5=20=D0=B8=D1=81=D1=82=D0=BE=D1=80=D0=B8=D0=B8=20=D0=BF?= =?UTF-8?q?=D0=B5=D1=80=D0=B5=D0=B4=D0=B0=D0=BD=D0=BD=D0=BE=D0=B9=20=D0=BA?= =?UTF-8?q?=D0=B0=D0=BA=20formData.=20=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8?= =?UTF-8?q?=D0=BB=D0=B0=20=D0=BF=D1=80=D0=BE=D0=B2=D0=B5=D1=80=D0=BA=D1=83?= =?UTF-8?q?=20=D0=B8=20=D0=BA=D0=BE=D0=BD=D0=B2=D0=B5=D1=80=D1=82=D0=B0?= =?UTF-8?q?=D1=86=D0=B8=D1=8E=20=D0=B4=D0=BB=D1=8F=20=D1=80=D1=83=D1=81?= =?UTF-8?q?=D1=81=D0=BA=D0=BE=D0=B9=20=D0=BA=D0=BE=D0=B4=D0=B8=D1=80=D0=BE?= =?UTF-8?q?=D0=B2=D0=BA=D0=B8.=20=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8?= =?UTF-8?q?=D0=BB=D0=B0=20=D1=81=D0=BE=D1=85=D1=80=D0=B0=D0=BD=D0=B5=D0=BD?= =?UTF-8?q?=D0=B8=D0=B5=20=D0=B8=D1=81=D1=82=D0=BE=D1=80=D0=B8=D0=B8=20?= =?UTF-8?q?=D0=B7=D0=B0=D0=B3=D1=80=D1=83=D0=B7=D0=BA=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/DownloadTrackRepository.java | 6 + .../service/impl/HistoryServiceImpl.java | 6 +- .../service/impl/TrackServiceImpl.java | 25 +- .../main/java/ownradio/util/DecodeUtil.java | 26 + .../web/rest/v3/HistoryController.java | 42 +- .../ownradio/web/rest/v3/TrackController.java | 15 +- .../main/resources/data/postgresql/schema.sql | 478 +++++++++++------- 7 files changed, 403 insertions(+), 195 deletions(-) create mode 100644 v3/src/main/java/ownradio/util/DecodeUtil.java diff --git a/v3/src/main/java/ownradio/repository/DownloadTrackRepository.java b/v3/src/main/java/ownradio/repository/DownloadTrackRepository.java index ef61be7..269e625 100644 --- a/v3/src/main/java/ownradio/repository/DownloadTrackRepository.java +++ b/v3/src/main/java/ownradio/repository/DownloadTrackRepository.java @@ -1,7 +1,10 @@ package ownradio.repository; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import ownradio.domain.Device; import ownradio.domain.DownloadTrack; +import ownradio.domain.Track; import java.util.UUID; @@ -11,4 +14,7 @@ * @author Alpenov Tanat */ public interface DownloadTrackRepository extends JpaRepository { + + DownloadTrack findFirstByDeviceAndTrackOrderByReccreatedAsc(Device device, Track track); + } diff --git a/v3/src/main/java/ownradio/service/impl/HistoryServiceImpl.java b/v3/src/main/java/ownradio/service/impl/HistoryServiceImpl.java index cf40353..32eff85 100644 --- a/v3/src/main/java/ownradio/service/impl/HistoryServiceImpl.java +++ b/v3/src/main/java/ownradio/service/impl/HistoryServiceImpl.java @@ -3,8 +3,10 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import ownradio.domain.DownloadTrack; import ownradio.domain.History; import ownradio.domain.Rating; +import ownradio.repository.DownloadTrackRepository; import ownradio.repository.HistoryRepository; import ownradio.repository.RatingRepository; import ownradio.service.HistoryService; @@ -14,11 +16,13 @@ public class HistoryServiceImpl implements HistoryService { private final HistoryRepository historyRepository; private final RatingRepository ratingRepository; + private final DownloadTrackRepository downloadTrackRepository; @Autowired - public HistoryServiceImpl(HistoryRepository historyRepository, RatingRepository ratingRepository) { + public HistoryServiceImpl(HistoryRepository historyRepository, RatingRepository ratingRepository, DownloadTrackRepository downloadTrackRepository) { this.historyRepository = historyRepository; this.ratingRepository = ratingRepository; + this.downloadTrackRepository = downloadTrackRepository; } @Transactional diff --git a/v3/src/main/java/ownradio/service/impl/TrackServiceImpl.java b/v3/src/main/java/ownradio/service/impl/TrackServiceImpl.java index b9b259f..80e5971 100644 --- a/v3/src/main/java/ownradio/service/impl/TrackServiceImpl.java +++ b/v3/src/main/java/ownradio/service/impl/TrackServiceImpl.java @@ -3,6 +3,9 @@ import com.mpatric.mp3agic.ID3v1; import com.mpatric.mp3agic.ID3v2; import com.mpatric.mp3agic.Mp3File; +import com.sun.xml.internal.fastinfoset.Decoder; +import com.sun.xml.internal.fastinfoset.util.CharArray; +import org.omg.IOP.Encoding; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -12,9 +15,9 @@ import ownradio.domain.Track; import ownradio.repository.TrackRepository; import ownradio.service.TrackService; +import ownradio.util.DecodeUtil; import ownradio.util.ResourceUtil; -import java.io.File; import java.util.List; import java.util.UUID; @@ -83,8 +86,6 @@ public void setTrackInfo(UUID trackid) { boolean artistFlag = false; boolean titleFlag = false; - byte[] buf; - if (trackid != null) { try { Track track = trackRepository.findOne(trackid); @@ -92,18 +93,14 @@ public void setTrackInfo(UUID trackid) { track.setLength((int) mp3File.getLengthInSeconds());//duration track track.setSize((int) mp3File.getLength() / 1024);//size in kilobytes - if (mp3File.hasId3v1Tag()) { - ID3v1 id3v1Tag1 = mp3File.getId3v1Tag(); - title = new String(id3v1Tag1.getTitle().getBytes("UTF16"),"Cp1251").replaceAll("\u0000", "").substring(2); -// title = id3v1Tag1.getTitle(); - artist = new String(id3v1Tag1.getArtist().getBytes("UTF16"),"Cp1251").replaceAll("\u0000", "").substring(2); -// artist = id3v1Tag1.getArtist(); - }else if (mp3File.hasId3v2Tag()) { + if (mp3File.hasId3v2Tag()) { ID3v2 id3v2Tag2 = mp3File.getId3v2Tag(); - title = id3v2Tag2.getTitle(); - title = title.equals(id3v2Tag2.getTitle()) ? title : null; - artist = id3v2Tag2.getArtist(); - artist = artist.equals(id3v2Tag2.getArtist()) ? artist : null; + title = DecodeUtil.Decode(id3v2Tag2.getTitle()) != null ? DecodeUtil.Decode(id3v2Tag2.getTitle()) : id3v2Tag2.getTitle(); + artist = DecodeUtil.Decode(id3v2Tag2.getArtist()) != null ? DecodeUtil.Decode(id3v2Tag2.getArtist()) : id3v2Tag2.getArtist(); + }else if (mp3File.hasId3v1Tag()) { + ID3v1 id3v1Tag1 = mp3File.getId3v1Tag(); + title = DecodeUtil.Decode(id3v1Tag1.getTitle()) != null ? DecodeUtil.Decode(id3v1Tag1.getTitle()) : id3v1Tag1.getTitle(); + artist = DecodeUtil.Decode(id3v1Tag1.getArtist()) != null ? DecodeUtil.Decode(id3v1Tag1.getArtist()) : id3v1Tag1.getArtist(); } if (title != null && !title.equals("null") && !title.isEmpty()) { diff --git a/v3/src/main/java/ownradio/util/DecodeUtil.java b/v3/src/main/java/ownradio/util/DecodeUtil.java new file mode 100644 index 0000000..3a13250 --- /dev/null +++ b/v3/src/main/java/ownradio/util/DecodeUtil.java @@ -0,0 +1,26 @@ +package ownradio.util; + +import com.mpatric.mp3agic.ID3v2; + +/** + * Created by a.polunina on 11.01.2017. + * Класс утилита для декодирования строк + */ +public class DecodeUtil { + + //Функция декодирует "Cp1251" в "UTF16" + public static String Decode(String str) { + try { + + char[] charArray = str.toCharArray(); + for (char characher : charArray) { + if (characher >= 'А' - 848 && characher <= 'ё' - 848) { + return new String(str.getBytes("UTF16"), "Cp1251").replaceAll("\u0000", "").substring(2); + } + } + return null; + } catch (Exception ex) { + return null; + } + } +} diff --git a/v3/src/main/java/ownradio/web/rest/v3/HistoryController.java b/v3/src/main/java/ownradio/web/rest/v3/HistoryController.java index f177382..057fa9d 100644 --- a/v3/src/main/java/ownradio/web/rest/v3/HistoryController.java +++ b/v3/src/main/java/ownradio/web/rest/v3/HistoryController.java @@ -14,6 +14,8 @@ import ownradio.service.HistoryService; import ownradio.service.TrackService; +import java.text.SimpleDateFormat; +import java.util.Calendar; import java.util.Date; import java.util.UUID; @@ -35,8 +37,46 @@ public HistoryController(HistoryService historyService, TrackService trackServic this.deviceService = deviceService; } + @Data + private static class HistoryDTO { + private UUID deviceId; + private UUID trackId; + private String lastListen; + private int isListen; // 1, -1 + private Integer methodid; + + public History getHistory() { + Calendar calendar; + try { + calendar = Calendar.getInstance(); + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'H:m:s"); + calendar.setTime( sdf.parse(lastListen)); + }catch (Exception ex){ + calendar = null; + } + + History history = new History(); + history.setLastListen(calendar); + history.setIsListen(isListen); + history.setMethodid(methodid); + + return history; + } + } + + //formData @RequestMapping(value = "/{deviceId}/{trackId}", method = RequestMethod.POST) - public ResponseEntity save(@PathVariable UUID deviceId, @PathVariable UUID trackId, @RequestBody History history) { + public ResponseEntity save(@PathVariable UUID deviceId, @PathVariable UUID trackId, HistoryDTO historyDTO) { + return getResponseEntity(deviceId, trackId, historyDTO.getHistory()); + } + + //json + @RequestMapping(value = "/{deviceId}/{trackId}", method = RequestMethod.POST, headers = "Content-Type=application/json") + public ResponseEntity save2(@PathVariable UUID deviceId, @PathVariable UUID trackId, @RequestBody History history) { + return getResponseEntity(deviceId, trackId, history); + } + + private ResponseEntity getResponseEntity(@PathVariable UUID deviceId, @PathVariable UUID trackId, @RequestBody History history) { try { log.info("{} {}",deviceId.toString(),trackId.toString()); log.info("{} {} {}",history.getLastListen(), history.getIsListen(), history.getMethod()); diff --git a/v3/src/main/java/ownradio/web/rest/v3/TrackController.java b/v3/src/main/java/ownradio/web/rest/v3/TrackController.java index 9fcbe4a..be8696c 100644 --- a/v3/src/main/java/ownradio/web/rest/v3/TrackController.java +++ b/v3/src/main/java/ownradio/web/rest/v3/TrackController.java @@ -9,8 +9,10 @@ import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; import ownradio.domain.Device; +import ownradio.domain.DownloadTrack; import ownradio.domain.NextTrack; import ownradio.domain.Track; +import ownradio.repository.DownloadTrackRepository; import ownradio.repository.TrackRepository; import ownradio.service.TrackService; import ownradio.util.ResourceUtil; @@ -28,11 +30,13 @@ public class TrackController { private final TrackService trackService; private final TrackRepository trackRepository; + private final DownloadTrackRepository downloadTrackRepository; @Autowired - public TrackController(TrackService trackService, TrackRepository trackRepository) { + public TrackController(TrackService trackService, TrackRepository trackRepository, DownloadTrackRepository downloadTrackRepository) { this.trackService = trackService; this.trackRepository = trackRepository; + this.downloadTrackRepository = downloadTrackRepository; } @Data @@ -126,6 +130,15 @@ public ResponseEntity getNextTrack(@PathVariable UUID deviceId) { trackInfo.put("artist", "Unknown artist"); trackInfo.put("methodid", nextTrack.getMethodid().toString()); + + //Сохраняем информацию об отданном треке + Device device = new Device(); + device.setRecid(deviceId); + DownloadTrack downloadTrack = new DownloadTrack(); + downloadTrack.setTrack(track); + downloadTrack.setDevice(device); + downloadTrackRepository.saveAndFlush(downloadTrack); + return new ResponseEntity<>(trackInfo, HttpStatus.OK); }catch (Exception ex){ log.info("{}", ex.getMessage()); diff --git a/v3/src/main/resources/data/postgresql/schema.sql b/v3/src/main/resources/data/postgresql/schema.sql index 6806721..4166698 100644 --- a/v3/src/main/resources/data/postgresql/schema.sql +++ b/v3/src/main/resources/data/postgresql/schema.sql @@ -1,176 +1,182 @@ CREATE OR REPLACE FUNCTION getnexttrackid(IN i_deviceid UUID) - RETURNS SETOF UUID AS + RETURNS SETOF UUID AS ' DECLARE - i_userid uuid = i_deviceid; - BEGIN - -- Добавляем устройство, если его еще не существует - -- Если ID устройства еще нет в БД - IF NOT EXISTS(SELECT recid - FROM devices - WHERE recid = i_deviceid) - THEN - - -- Добавляем нового пользователя - INSERT INTO users (recid, recname, reccreated) SELECT - i_userid, - ''New user recname'', - now(); - - -- Добавляем новое устройство - INSERT INTO devices (recid, userid, recname, reccreated) SELECT - i_deviceid, - i_userid, - ''New device recname'', - now(); - ELSE - SELECT (SELECT userid - FROM devices - WHERE recid = i_deviceid - LIMIT 1) - INTO i_userid; - END IF; - - RETURN QUERY - SELECT tracks.recid - FROM tracks - LEFT JOIN - ratings - ON tracks.recid = ratings.trackid AND ratings.userid = i_userid - WHERE ratings.ratingsum >=0 OR ratings.ratingsum is null - ORDER BY RANDOM() - LIMIT 1; + i_userid UUID = i_deviceid; +BEGIN + -- Добавляем устройство, если его еще не существует + -- Если ID устройства еще нет в БД + IF NOT EXISTS(SELECT recid + FROM devices + WHERE recid = i_deviceid) + THEN + + -- Добавляем нового пользователя + INSERT INTO users (recid, recname, reccreated) SELECT + i_userid, + ''New user recname'', + now(); + + -- Добавляем новое устройство + INSERT INTO devices (recid, userid, recname, reccreated) SELECT + i_deviceid, + i_userid, + ''New device recname'', + now(); + ELSE + SELECT (SELECT userid + FROM devices + WHERE recid = i_deviceid + LIMIT 1) + INTO i_userid; + END IF; + + RETURN QUERY + SELECT tracks.recid + FROM tracks + LEFT JOIN + ratings + ON tracks.recid = ratings.trackid AND ratings.userid = i_userid + WHERE ratings.ratingsum >= 0 OR ratings.ratingsum IS NULL + ORDER BY RANDOM() + LIMIT 1; END; ' LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION getnexttrackid_string(i_deviceid UUID) - RETURNS SETOF CHARACTER VARYING AS + RETURNS SETOF CHARACTER VARYING AS ' BEGIN - RETURN QUERY SELECT CAST(getnexttrackid(i_deviceid) AS CHARACTER VARYING); + RETURN QUERY SELECT CAST(getnexttrackid(i_deviceid) AS CHARACTER VARYING); END; ' LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION registertrack( - i_trackid UUID, - i_localdevicepathupload CHARACTER VARYING, - i_path CHARACTER VARYING, - i_deviceid UUID) - RETURNS BOOLEAN AS + i_trackid UUID, + i_localdevicepathupload CHARACTER VARYING, + i_path CHARACTER VARYING, + i_deviceid UUID) + RETURNS BOOLEAN AS ' DECLARE - i_userid UUID = i_deviceid; - i_historyid UUID; - i_ratingid UUID; + i_userid UUID = i_deviceid; + i_historyid UUID; + i_ratingid UUID; BEGIN - CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; - SELECT uuid_generate_v4() - INTO i_historyid; - SELECT uuid_generate_v4() - INTO i_ratingid; - - -- - -- Функция добавляет запись о треке в таблицу треков и делает сопутствующие записи в - -- таблицу статистики прослушивания и рейтингов. Если пользователя, загружающего трек - -- нет в базе, то он добавляется в таблицу пользователей. - -- - - -- Добавляем устройство, если его еще не существует - -- Если ID устройства еще нет в БД - IF NOT EXISTS(SELECT recid - FROM devices - WHERE recid = i_deviceid) - THEN - - -- Добавляем нового пользователя - INSERT INTO users (recid, recname, reccreated) SELECT - i_userid, - ''New user recname'', - now(); - - -- Добавляем новое устройство - INSERT INTO devices (recid, userid, recname, reccreated) SELECT - i_deviceid, - i_userid, - ''New device recname'', - now(); - ELSE - SELECT (SELECT userid - FROM devices - WHERE recid = i_deviceid - LIMIT 1) - INTO i_userid; - END IF; - - -- Добавляем трек в базу данных - INSERT INTO tracks (recid, localdevicepathupload, path, deviceid, reccreated, isexist) - VALUES (i_trackid, i_localdevicepathupload, i_path, i_deviceid, now(), 1); - - -- Добавляем запись о прослушивании трека в таблицу истории прослушивания - INSERT INTO histories (recid, deviceid, trackid, isListen, lastListen, methodid, reccreated) - VALUES (i_historyid, i_deviceid, i_trackid, 1, now(), 2, now()); - - -- Добавляем запись в таблицу рейтингов - INSERT INTO ratings (recid, userid, trackid, lastListen, ratingsum, reccreated) - VALUES (i_ratingid, i_userid, i_trackid, now(), 1, now()); - - RETURN TRUE; + CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; + SELECT uuid_generate_v4() + INTO i_historyid; + SELECT uuid_generate_v4() + INTO i_ratingid; + + -- + -- Функция добавляет запись о треке в таблицу треков и делает сопутствующие записи в + -- таблицу статистики прослушивания и рейтингов. Если пользователя, загружающего трек + -- нет в базе, то он добавляется в таблицу пользователей. + -- + + -- Добавляем устройство, если его еще не существует + -- Если ID устройства еще нет в БД + IF NOT EXISTS(SELECT recid + FROM devices + WHERE recid = i_deviceid) + THEN + + -- Добавляем нового пользователя + INSERT INTO users (recid, recname, reccreated) SELECT + i_userid, + ''New user recname'', + now(); + + -- Добавляем новое устройство + INSERT INTO devices (recid, userid, recname, reccreated) SELECT + i_deviceid, + i_userid, + ''New device recname'', + now(); + ELSE + SELECT (SELECT userid + FROM devices + WHERE recid = i_deviceid + LIMIT 1) + INTO i_userid; + END IF; + + -- Добавляем трек в базу данных + INSERT INTO tracks (recid, localdevicepathupload, path, deviceid, reccreated, iscensorial, isexist) + VALUES (i_trackid, i_localdevicepathupload, i_path, i_deviceid, now(), 2, 1); + + -- Добавляем запись о прослушивании трека в таблицу истории прослушивания + INSERT INTO histories (recid, deviceid, trackid, isListen, lastListen, methodid, reccreated) + VALUES (i_historyid, i_deviceid, i_trackid, 1, now(), 2, now()); + + -- Добавляем запись в таблицу рейтингов + INSERT INTO ratings (recid, userid, trackid, lastListen, ratingsum, reccreated) + VALUES (i_ratingid, i_userid, i_trackid, now(), 1, now()); + + RETURN TRUE; END; ' LANGUAGE plpgsql; -CREATE OR REPLACE FUNCTION getnexttrackid_v2(i_deviceid uuid) - RETURNS TABLE(track uuid, methodid integer) +CREATE OR REPLACE FUNCTION getnexttrackid_v2(i_deviceid UUID) + RETURNS TABLE(track UUID, methodid INTEGER) AS ' DECLARE - i_userid uuid = i_deviceid; - rnd integer = (select trunc(random() * 10)); -- получаем случайное число от 0 до 9 - o_methodid integer; -- id метода выбора трека + i_userid UUID = i_deviceid; + rnd INTEGER = (SELECT trunc(random() * 10)); -- получаем случайное число от 0 до 9 + o_methodid INTEGER; -- id метода выбора трека BEGIN - -- Выбираем следующий трек + -- Выбираем следующий трек - -- В 9/10 случаях выбираем трек из треков пользователя (добавленных им или прослушанных до конца) - -- с положительным рейтингом, за исключением прослушанных за последние сутки + -- В 9/10 случаях выбираем трек из треков пользователя (добавленных им или прослушанных до конца) + -- с положительным рейтингом, за исключением прослушанных за последние сутки IF (rnd > 1) THEN o_methodid = 2; RETURN QUERY - SELECT trackid, o_methodid - FROM ratings - WHERE userid = i_userid - AND lastlisten < localtimestamp - interval ''1 day'' - AND ratingsum >= 0 - AND (SELECT isexist FROM tracks WHERE recid = trackid) = 1 - ORDER BY RANDOM() - LIMIT 1; + SELECT + trackid, + o_methodid + FROM ratings + WHERE userid = i_userid + AND lastlisten < localtimestamp - INTERVAL ''1 day'' + AND ratingsum >= 0 + AND (SELECT isexist + FROM tracks + WHERE recid = trackid) = 1 + ORDER BY RANDOM() + LIMIT 1; -- Если такой трек найден - выход из функции, возврат найденного значения IF FOUND - THEN RETURN; + THEN RETURN; END IF; END IF; -- В 1/10 случае выбираем случайный трек из ни разу не прослушанных пользователем треков o_methodid = 3; RETURN QUERY - SELECT recid, o_methodid - FROM tracks - WHERE recid NOT IN - (SELECT trackid - FROM ratings - WHERE userid = i_userid) - AND isexist = 1 - ORDER BY RANDOM() - LIMIT 1; + SELECT + recid, + o_methodid + FROM tracks + WHERE recid NOT IN + (SELECT trackid + FROM ratings + WHERE userid = i_userid) + AND isexist = 1 + ORDER BY RANDOM() + LIMIT 1; - -- Если такой трек найден - выход из функции, возврат найденного значения + -- Если такой трек найден - выход из функции, возврат найденного значения IF FOUND THEN RETURN; END IF; @@ -178,33 +184,37 @@ BEGIN -- Если предыдущие запросы вернули null, выбираем случайный трек o_methodid = 1; RETURN QUERY - SELECT recid, o_methodid - FROM tracks - WHERE isexist = 1 - ORDER BY RANDOM() - LIMIT 1; + SELECT + recid, + o_methodid + FROM tracks + WHERE isexist = 1 + ORDER BY RANDOM() + LIMIT 1; RETURN; END; ' LANGUAGE plpgsql; -CREATE OR REPLACE FUNCTION getnexttrackid_v3(IN i_deviceid uuid) - RETURNS TABLE(track uuid, methodid integer) AS +CREATE OR REPLACE FUNCTION getnexttrackid_v3(IN i_deviceid UUID) + RETURNS TABLE(track UUID, methodid INTEGER) AS ' DECLARE - i_userid uuid = i_deviceid; - rnd integer = (select trunc(random() * 1001)); - o_methodid integer; -- id метода выбора трека - owntracks integer; -- количество "своих" треков пользователя (обрезаем на 900 шт) + i_userid UUID = i_deviceid; + rnd INTEGER = (SELECT trunc(random() * 1001)); + o_methodid INTEGER; -- id метода выбора трека + owntracks INTEGER; -- количество "своих" треков пользователя (обрезаем на 900 шт) BEGIN -- Выбираем следующий трек -- Определяем количество "своих" треков пользователя, ограничивая его 900 - owntracks = (SELECT COUNT(*) FROM ( - SELECT * FROM ratings - WHERE userid = i_userid - AND ratingsum >=0 - LIMIT 900) AS count) ; + owntracks = (SELECT COUNT(*) + FROM ( + SELECT * + FROM ratings + WHERE userid = i_userid + AND ratingsum >= 0 + LIMIT 900) AS count); -- Если rnd меньше количества "своих" треков, выбираем трек из треков пользователя (добавленных им или прослушанных до конца) -- с положительным рейтингом, за исключением прослушанных за последние сутки @@ -213,12 +223,16 @@ BEGIN THEN o_methodid = 2; -- метод выбора из своих треков RETURN QUERY - SELECT trackid, o_methodid - FROM ratings - WHERE userid = i_userid - AND lastlisten < localtimestamp - interval ''1 day'' - AND ratingsum >= 0 - AND (SELECT isexist FROM tracks WHERE recid = trackid) = 1 + SELECT + trackid, + o_methodid + FROM ratings + WHERE userid = i_userid + AND lastlisten < localtimestamp - INTERVAL ''1 day'' + AND ratingsum >= 0 + AND (SELECT isexist + FROM tracks + WHERE recid = trackid) = 1 ORDER BY RANDOM() LIMIT 1; @@ -231,42 +245,150 @@ BEGIN -- В 1/10 случае выбираем случайный трек из ни разу не прослушанных пользователем треков o_methodid = 3; -- метод выбора из непрослушанных треков RETURN QUERY - SELECT recid, o_methodid - FROM tracks - WHERE recid NOT IN - (SELECT trackid - FROM ratings - WHERE userid = i_userid) - AND isexist = 1 - ORDER BY RANDOM() + SELECT + recid, + o_methodid + FROM tracks + WHERE recid NOT IN + (SELECT trackid + FROM ratings + WHERE userid = i_userid) + AND isexist = 1 + ORDER BY RANDOM() LIMIT 1; -- Если такой трек найден - выход из функции, возврат найденного значения IF FOUND - THEN RETURN; + THEN RETURN; END IF; -- Если предыдущие запросы вернули null, выбираем случайный трек o_methodid = 1; -- метод выбора случайного трека RETURN QUERY - SELECT recid, o_methodid - FROM tracks - WHERE isexist = 1 - ORDER BY RANDOM() - LIMIT 1; - RETURN; + SELECT + recid, + o_methodid + FROM tracks + WHERE isexist = 1 + ORDER BY RANDOM() + LIMIT 1; + RETURN; END; ' LANGUAGE plpgsql; +CREATE OR REPLACE FUNCTION getnexttrackid_v5(IN i_deviceid UUID) + RETURNS TABLE(track UUID, methodid INTEGER) AS +' +DECLARE + i_userid UUID = i_deviceid; + rnd INTEGER = (SELECT trunc(random() * 1001)); + o_methodid INTEGER; -- id метода выбора трека + owntracks INTEGER; -- количество "своих" треков пользователя (обрезаем на 900 шт) +BEGIN + -- Выбираем следующий трек + + -- Определяем количество "своих" треков пользователя, ограничивая его 900 + owntracks = (SELECT COUNT(*) + FROM ( + SELECT * + FROM ratings + WHERE userid = i_userid + AND ratingsum >= 0 + LIMIT 900) AS count); + + -- Если rnd меньше количества "своих" треков, выбираем трек из треков пользователя (добавленных им или прослушанных до конца) + -- с положительным рейтингом, за исключением прослушанных за последние сутки + + IF (rnd < owntracks) + THEN + o_methodid = 2; -- метод выбора из своих треков + RETURN QUERY + SELECT + trackid, + o_methodid + FROM ratings + WHERE userid = i_userid + AND lastlisten < localtimestamp - INTERVAL ''1 day'' + AND ratingsum >= 0 + AND (SELECT isexist + FROM tracks + WHERE recid = trackid) = 1 + AND ((SELECT length + FROM tracks + WHERE recid = trackid) >= 120 + OR (SELECT length + FROM tracks + WHERE recid = trackid) IS NULL) + AND ((SELECT iscensorial + FROM tracks + WHERE recid = trackid) IS NULL + OR (SELECT iscensorial + FROM tracks + WHERE recid = trackid) != 0) + AND trackid NOT IN (SELECT trackid + FROM downloadtracks + WHERE reccreated < localtimestamp - INTERVAL ''1 day'') + ORDER BY RANDOM() + LIMIT 1; + + -- Если такой трек найден - выход из функции, возврат найденного значения + IF FOUND + THEN RETURN; + END IF; + END IF; + + -- В 1/10 случае выбираем случайный трек из ни разу не прослушанных пользователем треков + o_methodid = 3; -- метод выбора из непрослушанных треков + RETURN QUERY + SELECT + recid, + o_methodid + FROM tracks + WHERE recid NOT IN + (SELECT trackid + FROM ratings + WHERE userid = i_userid) + AND isexist = 1 + AND (iscensorial IS NULL OR iscensorial != 0) + AND (length > 120 OR length IS NULL) + AND recid NOT IN (SELECT trackid + FROM downloadtracks + WHERE reccreated < localtimestamp - INTERVAL ''1 day'') + ORDER BY RANDOM() + LIMIT 1; + + -- Если такой трек найден - выход из функции, возврат найденного значения + IF FOUND + THEN RETURN; + END IF; + + -- Если предыдущие запросы вернули null, выбираем случайный трек + o_methodid = 1; -- метод выбора случайного трека + RETURN QUERY + SELECT + recid, + o_methodid + FROM tracks + WHERE isexist = 1 + AND (iscensorial IS NULL OR iscensorial != 0) + AND (length > 120 OR length IS NULL) + ORDER BY RANDOM() + LIMIT 1; + RETURN; +END; +' +LANGUAGE plpgsql; + + CREATE OR REPLACE FUNCTION getnexttrack(i_deviceid UUID) - RETURNS TABLE( - track character varying - , methodid integer) - AS + RETURNS TABLE( + track CHARACTER VARYING + , methodid INTEGER) +AS ' DECLARE - i_userid uuid = i_deviceid; -- в дальнейшем заменить получением userid по deviceid + i_userid UUID = i_deviceid; -- в дальнейшем заменить получением userid по deviceid BEGIN -- Добавляем устройство, если его еще не существует -- Если ID устройства еще нет в БД @@ -277,16 +399,16 @@ BEGIN -- Добавляем нового пользователя INSERT INTO users (recid, recname, reccreated) SELECT - i_userid, - ''New user recname'', - now(); + i_userid, + ''New user recname'', + now(); -- Добавляем новое устройство INSERT INTO devices (recid, userid, recname, reccreated) SELECT - i_deviceid, - i_userid, - ''New device recname'', - now(); + i_deviceid, + i_userid, + ''New device recname'', + now(); ELSE SELECT (SELECT userid FROM devices @@ -302,4 +424,4 @@ BEGIN FROM getnexttrackid_v3(i_deviceid) AS nexttrack; END; ' -LANGUAGE plpgsql; \ No newline at end of file +LANGUAGE plpgsql; From d42d5b4e25fd1b0170803185ff38f4505a2a4989 Mon Sep 17 00:00:00 2001 From: Alexandra Polunina Date: Wed, 18 Jan 2017 11:42:19 +0300 Subject: [PATCH 21/44] =?UTF-8?q?=D0=92=D0=BD=D0=B5=D1=81=D0=BB=D0=B0=20?= =?UTF-8?q?=D0=B8=D0=B7=D0=BC=D0=B5=D0=BD=D0=B5=D0=BD=D0=B8=D1=8F=20=20?= =?UTF-8?q?=D0=B2=20=D1=84=D1=83=D0=BD=D0=BA=D1=86=D0=B8=D1=8E=20decode=20?= =?UTF-8?q?(=D1=83=D0=BB=D1=83=D1=87=D1=88=D0=B5=D0=BD=D0=B8=D1=8F=20?= =?UTF-8?q?=D0=A2=D0=B0=D0=BD=D0=B0=D1=82=D0=B0).=20=D0=9F=D0=BE=D0=BF?= =?UTF-8?q?=D1=80=D0=B0=D0=B2=D0=B8=D0=BB=D0=B0=20=D1=87=D1=82=D0=B5=D0=BD?= =?UTF-8?q?=D0=B8=D0=B5=20=D1=82=D0=B5=D0=B3=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/impl/TrackServiceImpl.java | 20 ++++++++++-------- .../main/java/ownradio/util/DecodeUtil.java | 21 ++++++++++--------- 2 files changed, 22 insertions(+), 19 deletions(-) diff --git a/v3/src/main/java/ownradio/service/impl/TrackServiceImpl.java b/v3/src/main/java/ownradio/service/impl/TrackServiceImpl.java index 80e5971..dffa81b 100644 --- a/v3/src/main/java/ownradio/service/impl/TrackServiceImpl.java +++ b/v3/src/main/java/ownradio/service/impl/TrackServiceImpl.java @@ -95,29 +95,31 @@ public void setTrackInfo(UUID trackid) { if (mp3File.hasId3v2Tag()) { ID3v2 id3v2Tag2 = mp3File.getId3v2Tag(); - title = DecodeUtil.Decode(id3v2Tag2.getTitle()) != null ? DecodeUtil.Decode(id3v2Tag2.getTitle()) : id3v2Tag2.getTitle(); - artist = DecodeUtil.Decode(id3v2Tag2.getArtist()) != null ? DecodeUtil.Decode(id3v2Tag2.getArtist()) : id3v2Tag2.getArtist(); - }else if (mp3File.hasId3v1Tag()) { + title = DecodeUtil.Decode(id3v2Tag2.getTitle()); + artist = DecodeUtil.Decode(id3v2Tag2.getArtist()); + } + if(mp3File.hasId3v1Tag()) { ID3v1 id3v1Tag1 = mp3File.getId3v1Tag(); - title = DecodeUtil.Decode(id3v1Tag1.getTitle()) != null ? DecodeUtil.Decode(id3v1Tag1.getTitle()) : id3v1Tag1.getTitle(); - artist = DecodeUtil.Decode(id3v1Tag1.getArtist()) != null ? DecodeUtil.Decode(id3v1Tag1.getArtist()) : id3v1Tag1.getArtist(); + if(title == null || title.equals("null") || title.isEmpty()) + title = DecodeUtil.Decode(id3v1Tag1.getTitle()); + if(artist == null || artist.equals("null") || artist.isEmpty()) + artist = DecodeUtil.Decode(id3v1Tag1.getArtist()); } if (title != null && !title.equals("null") && !title.isEmpty()) { track.setRecname(title.replaceAll("\u0000", "")); titleFlag = true; - } else - titleFlag = false; + } if (artist != null && !artist.equals("null") && !artist.isEmpty()) { track.setArtist(artist.replaceAll("\u0000", "")); artistFlag = true; - } else - artistFlag = false; + } if (artistFlag && titleFlag) track.setIsfilledinfo(1); trackRepository.saveAndFlush(track); } catch (Exception ex) { + ex.printStackTrace(); } } } diff --git a/v3/src/main/java/ownradio/util/DecodeUtil.java b/v3/src/main/java/ownradio/util/DecodeUtil.java index 3a13250..048cc97 100644 --- a/v3/src/main/java/ownradio/util/DecodeUtil.java +++ b/v3/src/main/java/ownradio/util/DecodeUtil.java @@ -2,25 +2,26 @@ import com.mpatric.mp3agic.ID3v2; +import java.io.UnsupportedEncodingException; + /** * Created by a.polunina on 11.01.2017. * Класс утилита для декодирования строк */ public class DecodeUtil { - //Функция декодирует "Cp1251" в "UTF16" + //Функция декодирует "Cp1252" в "Cp1251" public static String Decode(String str) { + if(str == null) + return str; try { - - char[] charArray = str.toCharArray(); - for (char characher : charArray) { - if (characher >= 'А' - 848 && characher <= 'ё' - 848) { - return new String(str.getBytes("UTF16"), "Cp1251").replaceAll("\u0000", "").substring(2); - } + if (str.chars().anyMatch(c -> c >= 'А' - 848 && c <= 'ё' - 848)) { + return new String(str.getBytes("Cp1252"), "Cp1251"); } - return null; - } catch (Exception ex) { - return null; + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); } + + return str; } } From 7fdec2b95e227517b6afc551d21ef935dce1b43e Mon Sep 17 00:00:00 2001 From: Alexandra Polunina Date: Wed, 18 Jan 2017 11:54:15 +0300 Subject: [PATCH 22/44] =?UTF-8?q?=D0=A3=D0=B1=D1=80=D0=B0=D0=BB=D0=B0=20?= =?UTF-8?q?=D0=BB=D0=B8=D1=88=D0=BD=D0=B5=D0=B5=20=D0=BF=D0=BE=D0=B4=D0=BA?= =?UTF-8?q?=D0=BB=D1=8E=D1=87=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=B1=D0=B8=D0=B1?= =?UTF-8?q?=D0=BB=D0=B8=D0=BE=D1=82=D0=B5=D0=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- v3/src/main/java/ownradio/service/impl/TrackServiceImpl.java | 5 +---- v3/src/main/java/ownradio/util/DecodeUtil.java | 2 -- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/v3/src/main/java/ownradio/service/impl/TrackServiceImpl.java b/v3/src/main/java/ownradio/service/impl/TrackServiceImpl.java index dffa81b..8999058 100644 --- a/v3/src/main/java/ownradio/service/impl/TrackServiceImpl.java +++ b/v3/src/main/java/ownradio/service/impl/TrackServiceImpl.java @@ -3,9 +3,6 @@ import com.mpatric.mp3agic.ID3v1; import com.mpatric.mp3agic.ID3v2; import com.mpatric.mp3agic.Mp3File; -import com.sun.xml.internal.fastinfoset.Decoder; -import com.sun.xml.internal.fastinfoset.util.CharArray; -import org.omg.IOP.Encoding; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -119,7 +116,7 @@ public void setTrackInfo(UUID trackid) { track.setIsfilledinfo(1); trackRepository.saveAndFlush(track); } catch (Exception ex) { - ex.printStackTrace(); +// ex.printStackTrace(); } } } diff --git a/v3/src/main/java/ownradio/util/DecodeUtil.java b/v3/src/main/java/ownradio/util/DecodeUtil.java index 048cc97..12dda7a 100644 --- a/v3/src/main/java/ownradio/util/DecodeUtil.java +++ b/v3/src/main/java/ownradio/util/DecodeUtil.java @@ -1,7 +1,5 @@ package ownradio.util; -import com.mpatric.mp3agic.ID3v2; - import java.io.UnsupportedEncodingException; /** From f9cc11b50fa303e01cbbb67bdb1ac67ef00868e4 Mon Sep 17 00:00:00 2001 From: Alexandra Polunina Date: Thu, 19 Jan 2017 12:32:19 +0300 Subject: [PATCH 23/44] =?UTF-8?q?=D0=9F=D0=BE=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=B8=D0=BB=D0=B0=20readme.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 70 ++++++++++++++++++++++++++++++- v3/README.md | 113 --------------------------------------------------- 2 files changed, 69 insertions(+), 114 deletions(-) delete mode 100644 v3/README.md diff --git a/README.md b/README.md index dad8ab0..beaba71 100644 --- a/README.md +++ b/README.md @@ -1 +1,69 @@ -# Server.Java \ No newline at end of file +# Java сервер для OwnRadio + +Адрес сервера api.ownradio.ru + +Этот проект разрабатывается с целью тестирования и обкатки Java технологий + +Run +--- +##### Запускаем с помощью maven +* $ mvn -Dupload.dir=c:\ -Dspring.profiles.active=dev -Dserver.port=8080 spring-boot:run +* $ mvn -Dupload.dir=c:\ -Dspring.profiles.active=prod -Dserver.port=8080 spring-boot:run + +##### Запускаем упакованный jar +* $ java -Dupload.dir=c:\ -jar ownradio.jar --spring.profiles.active=dev --server.port=8080 +* $ java -Dupload.dir=c:\ -jar ownradio.jar --spring.profiles.active=prod --server.port=8080 + +Web API v3 +--- + +### Загрузка трека на сервер + +##### POST /v3/tracks +* `fileGuid` – UUID трека +* `filePath` - Полный локальный путь к файлу на пользовательском устройстве, включая имя файла (String) +* `deviceId` – UUID device +* `musicFile` – прикрепленный файл + +##### HttpStatus +* `400, "Bad Request"` - Если пользователь ввел некорректные данные +* `201, "Created"` – если все ок +* `500, "Internal Server Error"` – если произошел сбой на сервере + +### Получение трека с сервера +##### GET /v3/tracks/{trackId} +* `{trackId}` – UUID трека + +##### HttpStatus +* `200, "OK"` – в теле ответа будет лежать трек +* `404, "Not Found"` – если трек с таким recid не найден + +### Получение следующего трека с сервера +##### GET /v3/tracks/{deviceId}/next +* `{deviceId}` – UUID девайса + +##### Response +Content-Type →application/json;charset=UTF-8 +{ + "artist": "Artist", + "length": "duration", + "name": "Title", + "methodid": "1", + "id": "00000000-0000-0000-0000-000000000000" +} + +##### HttpStatus +* `200, "OK"` – в теле ответа будет лежать UUID трека +* `404, "Not Found"` – если трек с таким recid не найден + +### Сохранение истории треков +##### POST /v3/histories/{deviceId}/{trackId} +* `{trackId}` – UUID прослушанного трека +* `{deviceId}` – UUID устройства где был прослушан трек +* `lastListen` - Время последнего прослушивания или пропуска трека для данного пользователя ("yyyy-MM-ddTHH:mm:ss") +* `isListen` - Признак прослушан ли трек до конца: 1 - прослушан, -1 – нет (int) +* `methodid` - ID метода выбора трека (int), равен значению, полученному при получении данных для следующего трека + +##### HttpStatus +* `200, "OK"` – если все ок +* `500, "Internal Server Error"` – если произошел сбой на сервере diff --git a/v3/README.md b/v3/README.md deleted file mode 100644 index d663e2b..0000000 --- a/v3/README.md +++ /dev/null @@ -1,113 +0,0 @@ -# Java сервер для OwnRadio - -Этот проект разрабатывается с целью тестирования и обкатки Java технологий - -Run ---- -##### Запускаем с помощью maven -* $ mvn -Dupload.dir=c:\ -Dspring.profiles.active=dev -Dserver.port=8080 spring-boot:run -* $ mvn -Dupload.dir=c:\ -Dspring.profiles.active=prod -Dserver.port=8080 spring-boot:run - -##### Запускаем упакованный jar -* $ java -Dupload.dir=c:\ -jar ownradio.jar --spring.profiles.active=dev --server.port=8080 -* $ java -Dupload.dir=c:\ -jar ownradio.jar --spring.profiles.active=prod --server.port=8080 - - -Web API ---- - -### Загрузка трека на сервер - -##### POST /v2/tracks -* `fileGuid` – UUID трека -* `fileName` – имя файла -* `filePath` - Локальный путь к файлу на пользовательском устройстве -* `deviceId` – UUID device -* `musicFile` – прикрепленный файл - -##### HttpStatus -* `400, "Bad Request"` - Если пользователь ввел некорректные данные -* `201, "Created"` – если все ок -* `500, "Internal Server Error"` – если произошел сбой на сервере - -### Получение трека с сервера -##### GET /v2/tracks/{trackId} -* `{trackId}` – UUID трека - -##### HttpStatus -* `200, "OK"` – в теле ответа будет лежать трек -* `404, "Not Found"` – если трек с таким recid не найден - -### Получение следующего трека с сервера -##### GET /v2/tracks/{deviceId}/next -* `{deviceId}` – UUID девайса - -##### HttpStatus -* `200, "OK"` – в теле ответа будет лежать UUID трека -* `404, "Not Found"` – если трек с таким recid не найден - -### Сохранение истории треков -##### POST /v2/histories/{deviceId}/{trackId} -* `{trackId}` – UUID прослушанного трека -* `{deviceId}` – UUID устройства где был прослушан трек -* `lastListen` - Время последнего прослушивания или пропуска трека для данного пользователя -* `isListen` - Признак прослушан ли трек до конца: 1 - прослушан, -1 – нет -* `method` - Метод выбора трека - -##### HttpStatus -* `200, "OK"` – если все ок -* `500, "Internal Server Error"` – если произошел сбой на сервере - -Web API v3 ---- - -### Загрузка трека на сервер - -##### POST /v3/tracks -* `fileGuid` – UUID трека -* `filePath` - Полный локальный путь к файлу на пользовательском устройстве, включая имя файла (String) -* `deviceId` – UUID device -* `musicFile` – прикрепленный файл - -##### HttpStatus -* `400, "Bad Request"` - Если пользователь ввел некорректные данные -* `201, "Created"` – если все ок -* `500, "Internal Server Error"` – если произошел сбой на сервере - -### Получение трека с сервера -##### GET /v3/tracks/{trackId} -* `{trackId}` – UUID трека - -##### HttpStatus -* `200, "OK"` – в теле ответа будет лежать трек -* `404, "Not Found"` – если трек с таким recid не найден - -### Получение следующего трека с сервера -##### GET /v3/tracks/{deviceId}/next -* `{deviceId}` – UUID девайса - -##### Response -Content-Type →application/json;charset=UTF-8 -{ - "artist": "Artist", - "length": "duration", - "name": "Title", - "methodid": "1", - "id": "00000000-0000-0000-0000-000000000000" -} - -##### HttpStatus -* `200, "OK"` – в теле ответа будет лежать UUID трека -* `404, "Not Found"` – если трек с таким recid не найден - -### Сохранение истории треков -##### POST /v3/histories/{deviceId}/{trackId} -* `{trackId}` – UUID прослушанного трека -* `{deviceId}` – UUID устройства где был прослушан трек -* `lastListen` - Время последнего прослушивания или пропуска трека для данного пользователя ("yyyy-MM-ddTHH:mm:ss") -* `isListen` - Признак прослушан ли трек до конца: 1 - прослушан, -1 – нет (int) -* `methodid` - ID метода выбора трека (int), равен значению, полученному при получении данных для следующего трека - -##### HttpStatus -* `200, "OK"` – если все ок -* `500, "Internal Server Error"` – если произошел сбой на сервере From ffcc34a973f47ec21e21c7a24c500ab11bab1450 Mon Sep 17 00:00:00 2001 From: Alexandra Polunina Date: Mon, 30 Jan 2017 11:08:09 +0300 Subject: [PATCH 24/44] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8?= =?UTF-8?q?=D0=BB=D0=B0=20=D1=81=D0=BA=D1=80=D0=B8=D0=BF=D1=82=D1=8B=20?= =?UTF-8?q?=D1=84=D1=83=D0=BD=D1=86=D0=B8=D0=B9=20calculateratios(),=20sel?= =?UTF-8?q?ectdownloadhistory(uuid),=20getnexttrackid=5Fv5(uuid),=20getnex?= =?UTF-8?q?ttrackid=5Fv6(uuid)=20=D0=B8=20=D1=82=D0=B0=D0=B1=D0=BB=D0=B8?= =?UTF-8?q?=D1=86=D1=8B=20ratios?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dbScripts/ownradio_db_v3.sql | 430 +++++++++++++++++++++++++++++++++++ 1 file changed, 430 insertions(+) diff --git a/dbScripts/ownradio_db_v3.sql b/dbScripts/ownradio_db_v3.sql index 0c7e6bc..4ab62aa 100644 --- a/dbScripts/ownradio_db_v3.sql +++ b/dbScripts/ownradio_db_v3.sql @@ -236,6 +236,23 @@ WITH ( ALTER TABLE public.users OWNER TO postgres; +-- Table: public.ratios + +-- DROP TABLE public.ratios; + +CREATE TABLE public.ratios +( + recid uuid NOT NULL DEFAULT uuid_generate_v4(), + userid1 uuid NOT NULL, + userid2 uuid NOT NULL, + ratio integer, + CONSTRAINT ratios_pkey PRIMARY KEY (recid) +) +WITH ( + OIDS=FALSE +); +ALTER TABLE public.ratios + OWNER TO postgres; -- Function: public.getnexttrack(uuid) @@ -505,8 +522,313 @@ $BODY$ ROWS 1000; ALTER FUNCTION public.getnexttrackid_v3(uuid) OWNER TO postgres; + + +-- Function: public.getnexttrackid_v5(uuid) + +-- DROP FUNCTION public.getnexttrackid_v5(uuid); + +CREATE OR REPLACE FUNCTION public.getnexttrackid_v5(IN i_deviceid uuid) + RETURNS TABLE(track uuid, methodid integer) AS +$BODY$ +DECLARE + i_userid UUID = i_deviceid; + rnd INTEGER = (SELECT trunc(random() * 1001)); + o_methodid INTEGER; -- id метода выбора трека + owntracks INTEGER; -- количество "своих" треков пользователя (обрезаем на 900 шт) +BEGIN + -- Выбираем следующий трек + + -- Определяем количество "своих" треков пользователя, ограничивая его 900 + owntracks = (SELECT COUNT(*) + FROM ( + SELECT * + FROM ratings + WHERE userid = i_userid + AND ratingsum >= 0 + LIMIT 900) AS count); + + -- Если rnd меньше количества "своих" треков, выбираем трек из треков пользователя (добавленных им или прослушанных до конца) + -- с положительным рейтингом, за исключением прослушанных за последние сутки + + IF (rnd < owntracks) + THEN + o_methodid = 2; -- метод выбора из своих треков + RETURN QUERY + SELECT + trackid, + o_methodid + FROM ratings + WHERE userid = i_userid + AND lastlisten < localtimestamp - INTERVAL '1 day' + AND ratingsum >= 0 + AND (SELECT isexist + FROM tracks + WHERE recid = trackid) = 1 + AND ((SELECT length + FROM tracks + WHERE recid = trackid) >= 120 + OR (SELECT length + FROM tracks + WHERE recid = trackid) IS NULL) + AND ((SELECT iscensorial + FROM tracks + WHERE recid = trackid) IS NULL + OR (SELECT iscensorial + FROM tracks + WHERE recid = trackid) != 0) + AND trackid NOT IN (SELECT trackid + FROM downloadtracks + WHERE reccreated < localtimestamp - INTERVAL '1 day') + ORDER BY RANDOM() + LIMIT 1; + + -- Если такой трек найден - выход из функции, возврат найденного значения + IF FOUND + THEN RETURN; + END IF; + END IF; + + -- Если rnd больше количества "своих" треков - выбираем случайный трек из ни разу не прослушанных пользователем треков + o_methodid = 3; -- метод выбора из непрослушанных треков + RETURN QUERY + SELECT + recid, + o_methodid + FROM tracks + WHERE recid NOT IN + (SELECT trackid + FROM ratings + WHERE userid = i_userid) + AND isexist = 1 + AND (iscensorial IS NULL OR iscensorial != 0) + AND (length > 120 OR length IS NULL) + AND recid NOT IN (SELECT trackid + FROM downloadtracks + WHERE reccreated < localtimestamp - INTERVAL '1 day') + ORDER BY RANDOM() + LIMIT 1; + + -- Если такой трек найден - выход из функции, возврат найденного значения + IF FOUND + THEN RETURN; + END IF; + + -- Если предыдущие запросы вернули null, выбираем случайный трек + o_methodid = 1; -- метод выбора случайного трека + RETURN QUERY + SELECT + recid, + o_methodid + FROM tracks + WHERE isexist = 1 + AND (iscensorial IS NULL OR iscensorial != 0) + AND (length > 120 OR length IS NULL) + ORDER BY RANDOM() + LIMIT 1; + RETURN; +END; +$BODY$ + LANGUAGE plpgsql VOLATILE + COST 100 + ROWS 1000; +ALTER FUNCTION public.getnexttrackid_v5(uuid) + OWNER TO postgres; + +-- Function: public.getnexttrackid_v6(uuid) + +-- DROP FUNCTION public.getnexttrackid_v6(uuid); + +CREATE OR REPLACE FUNCTION public.getnexttrackid_v6(IN i_deviceid uuid) + RETURNS TABLE(track uuid, methodid integer) AS +$BODY$ +DECLARE + i_userid UUID = i_deviceid; + rnd INTEGER = (SELECT trunc(random() * 1001)); + o_methodid INTEGER; -- id метода выбора трека + owntracks INTEGER; -- количество "своих" треков пользователя (обрезаем на 900 шт) + arrusers uuid ARRAY; -- массив пользователей для i_userid с неотрицательнымм коэффициентами схожести интересов +BEGIN + -- Выбираем следующий трек + + -- Определяем количество "своих" треков пользователя, ограничивая его 900 + owntracks = (SELECT COUNT(*) + FROM ( + SELECT * + FROM ratings + WHERE userid = i_userid + AND ratingsum >= 0 + LIMIT 900) AS count); + + -- Если rnd меньше количества "своих" треков, выбираем трек из треков пользователя (добавленных им или прослушанных до конца) + -- с положительным рейтингом, за исключением прослушанных за последние сутки + + IF (rnd < owntracks) + THEN + o_methodid = 2; -- метод выбора из своих треков + RETURN QUERY + SELECT + trackid, + o_methodid + FROM ratings + WHERE userid = i_userid + AND lastlisten < localtimestamp - INTERVAL '1 day' + AND ratingsum >= 0 + AND (SELECT isexist + FROM tracks + WHERE recid = trackid) = 1 + AND ((SELECT length + FROM tracks + WHERE recid = trackid) >= 120 + OR (SELECT length + FROM tracks + WHERE recid = trackid) IS NULL) + AND ((SELECT iscensorial + FROM tracks + WHERE recid = trackid) IS NULL + OR (SELECT iscensorial + FROM tracks + WHERE recid = trackid) != 0) + AND trackid NOT IN (SELECT trackid + FROM downloadtracks + WHERE reccreated < localtimestamp - INTERVAL '1 day') + ORDER BY RANDOM() + LIMIT 1; + + -- Если такой трек найден - выход из функции, возврат найденного значения + IF FOUND + THEN RETURN; + END IF; + END IF; + + -- Если rnd больше количества "своих" треков - рекомендуем трек из треков пользователя с наибольшим + -- коэффициентом схожести интересов и наибольшим рейтингом прослушивания + + -- Выберем всех пользователей с неотрицательным коэффициентом схожести интересов для i_userid + -- отсортировав по убыванию коэффициентов + arrusers = (SELECT ARRAY (SELECT CASE WHEN userid1 = i_userid THEN userid2 + WHEN userid2 = i_userid THEN userid1 + ELSE NULL + END + FROM ratios + WHERE userid1 = i_userid OR userid2 = i_userid + AND ratio >= 0 + ORDER BY ratio DESC + )); + -- Выбираем пользователя i, с которым у него максимальный коэффициент. Среди его треков ищем трек + -- с максимальным рейтингом прослушивания, за исключением уже прослушанных пользователем i_userid. + -- Если рекомендовать нечего - берем следующего пользователя с наибольшим коэффициентом из оставшихся. + FOR i IN 1.. (SELECT COUNT (*) FROM unnest(arrusers)) LOOP + o_methodid = 4; -- метод выбора из рекомендованных треков + RETURN QUERY + SELECT + trackid, + o_methodid + FROM ratings + WHERE userid = arrusers[i] + AND ratingsum > 0 + AND trackid NOT IN (SELECT trackid FROM ratings WHERE userid = i_userid) + AND trackid NOT IN (SELECT trackid + FROM downloadtracks + WHERE userid = i_userid + AND reccreated < localtimestamp - INTERVAL '1 day') + AND (SELECT isexist + FROM tracks + WHERE recid = trackid) = 1 + AND ((SELECT length + FROM tracks + WHERE recid = trackid) >= 120 + OR (SELECT length + FROM tracks + WHERE recid = trackid) IS NULL) + AND ((SELECT iscensorial + FROM tracks + WHERE recid = trackid) IS NULL + OR (SELECT iscensorial + FROM tracks + WHERE recid = trackid) != 0) + ORDER BY ratingsum DESC + LIMIT 1; + -- Если нашли что рекомендовать - выходим из функции + IF found THEN + RETURN; + END IF; + END LOOP; + + -- При отсутствии рекомендаций, выдавать случайный трек из непрослушанных треков с неотрицательным + -- рейтингом среди пользователей со схожим вкусом. + FOR i IN 1.. (SELECT COUNT (*) FROM unnest(arrusers)) LOOP + o_methodid = 5; -- метод выбора из непрослушанных треков с неотрицательным рейтингом среди пользователей со схожим вкусом + RETURN QUERY + SELECT + recid, + o_methodid + FROM tracks + WHERE recid NOT IN (SELECT trackid FROM ratings WHERE userid = arrusers[i] AND ratingsum < 0) + AND recid NOT IN (SELECT trackid FROM ratings WHERE userid = i_userid) + AND isexist = 1 + AND (iscensorial IS NULL OR iscensorial != 0) + AND (length > 120 OR length IS NULL) + AND recid NOT IN (SELECT trackid + FROM downloadtracks + WHERE reccreated < localtimestamp - INTERVAL '1 day') + ORDER BY RANDOM() + LIMIT 1; + -- Если нашли что рекомендовать - выходим из функции + IF found THEN + RETURN; + END IF; + END LOOP; + + -- Если таких треков нет - выбираем случайный трек из ни разу не прослушанных пользователем треков + o_methodid = 3; -- метод выбора из непрослушанных треков + RETURN QUERY + SELECT + recid, + o_methodid + FROM tracks + WHERE recid NOT IN + (SELECT trackid + FROM ratings + WHERE userid = i_userid) + AND isexist = 1 + AND (iscensorial IS NULL OR iscensorial != 0) + AND (length > 120 OR length IS NULL) + AND recid NOT IN (SELECT trackid + FROM downloadtracks + WHERE reccreated < localtimestamp - INTERVAL '1 day') + ORDER BY RANDOM() + LIMIT 1; + + -- Если такой трек найден - выход из функции, возврат найденного значения + IF FOUND + THEN RETURN; + END IF; + + -- Если предыдущие запросы вернули null, выбираем случайный трек + o_methodid = 1; -- метод выбора случайного трека + RETURN QUERY + SELECT + recid, + o_methodid + FROM tracks + WHERE isexist = 1 + AND (iscensorial IS NULL OR iscensorial != 0) + AND (length > 120 OR length IS NULL) + ORDER BY RANDOM() + LIMIT 1; + RETURN; +END; +$BODY$ + LANGUAGE plpgsql VOLATILE + COST 100 + ROWS 1000; +ALTER FUNCTION public.getnexttrackid_v6(uuid) + OWNER TO postgres; + + -- Function: public.registertrack(uuid, character varying, character varying, uuid) -- DROP FUNCTION public.registertrack(uuid, character varying, character varying, uuid); @@ -582,3 +904,111 @@ $BODY$ ALTER FUNCTION public.registertrack(uuid, character varying, character varying, uuid) OWNER TO postgres; + +-- Function: public.selectdownloadhistory(uuid) + +-- DROP FUNCTION public.selectdownloadhistory(uuid); + +CREATE OR REPLACE FUNCTION public.selectdownloadhistory(IN i_deviceid uuid) + RETURNS TABLE(recid uuid, reccreated timestamp without time zone, recname character varying, recupdated timestamp without time zone, deviceid uuid, trackid uuid, isstatisticback integer) AS +$BODY$ + BEGIN + -- Выводит список треков по которым не была отдана история прослушивания для данного устройства + RETURN QUERY SELECT * FROM downloadtracks + WHERE + downloadtracks.trackid NOT IN + (SELECT histories.trackid FROM histories WHERE histories.deviceid = i_deviceid) + AND downloadtracks.deviceid = i_deviceid; + + END; + $BODY$ + LANGUAGE plpgsql VOLATILE + COST 100 + ROWS 1000; +ALTER FUNCTION public.selectdownloadhistory(uuid) + OWNER TO postgres; + +-- Function: public.calculateratios() + +-- DROP FUNCTION public.calculateratios(); + +CREATE OR REPLACE FUNCTION public.calculateratios() + RETURNS void AS +$BODY$ +-- Функция рассчитывает таблицу коэффициентов схожести интересов для пар пользователей +DECLARE + user1 uuid; + user2 uuid; + usercount integer = (SELECT COUNT(*) FROM users); + arrusers uuid ARRAY = (SELECT ARRAY (SELECT recid FROM users)); +BEGIN + +FOR i IN 1..usercount - 1 LOOP + FOR j IN i+1..usercount LOOP + user1 = arrusers[i]; + user2 = arrusers[j]; + + + UPDATE ratios set ratio = + (SELECT SUM(ratio.ratingsum1 * ratio.ratingsum2) FROM ( + SELECT DISTINCT recid, + + (SELECT ratingsum + FROM ratings + WHERE (trackid = t.recid) + AND (userid = user1)) AS ratingsum1, + + (SELECT ratingsum + FROM ratings + WHERE (trackid = t.recid) + AND (userid = user2)) AS ratingsum2 + + FROM tracks AS t + WHERE recid IN (SELECT trackid + FROM ratings + WHERE trackid IN (SELECT trackid FROM ratings WHERE userid = user1) + AND userid = user2) + ) AS ratio) + + WHERE (userid1 = user1 AND userid2 = user2) + OR (userid1 = user2 AND userid2 = user1); + + -- Если таблица была обновлена - выход из функции, иначе - добавим запись + IF found THEN + CONTINUE; + END IF; + + INSERT INTO public.ratios( + userid1, userid2, ratio) + VALUES(user1, user2, + (SELECT SUM(ratio.ratingsum1 * ratio.ratingsum2) FROM ( + SELECT DISTINCT recid, + + (SELECT ratingsum + FROM ratings + WHERE (trackid = t.recid) + AND (userid = user1)) AS ratingsum1, + + (SELECT ratingsum + FROM ratings + WHERE (trackid = t.recid) + AND (userid = user2)) AS ratingsum2 + + FROM tracks AS t + WHERE recid IN (SELECT trackid + FROM ratings + WHERE trackid IN (SELECT trackid FROM ratings WHERE userid = user1) + AND userid = user2) + ) AS ratio)); + + END LOOP; +END LOOP; + +END; +$BODY$ + LANGUAGE plpgsql VOLATILE + COST 100; +ALTER FUNCTION public.calculateratios() + OWNER TO postgres; + + From 4bbfe0a42488f2c5215a3027fab800e3af1f9ea8 Mon Sep 17 00:00:00 2001 From: Alexandra Polunina Date: Fri, 3 Mar 2017 15:58:34 +0300 Subject: [PATCH 25/44] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8?= =?UTF-8?q?=D0=BB=D0=B0=20=D0=BE=D0=B1=D0=BD=D0=BE=D0=B2=D0=BB=D0=B5=D0=BD?= =?UTF-8?q?=D0=B8=D0=B5=20=D0=BC=D0=B0=D1=82=D1=80=D0=B8=D1=86=D1=8B=20?= =?UTF-8?q?=D0=BA=D0=BE=D1=8D=D1=84=D1=84=D0=B8=D1=86=D0=B8=D0=B5=D0=BD?= =?UTF-8?q?=D1=82=D0=BE=D0=B2=20=D1=81=D1=85=D0=BE=D0=B6=D0=B5=D1=81=D1=82?= =?UTF-8?q?=D0=B8=20=D0=B8=D0=BD=D1=82=D0=B5=D1=80=D0=B5=D1=81=D0=BE=D0=B2?= =?UTF-8?q?=20=D0=BF=D1=80=D0=B8=20=D1=81=D0=BE=D1=85=D1=80=D0=B0=D0=BD?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D0=B8=20=D1=81=D1=82=D0=B0=D1=82=D1=83=D1=81?= =?UTF-8?q?=D0=B0=20=D0=BF=D1=80=D0=BE=D1=81=D0=BB=D1=83=D1=88=D0=B8=D0=B2?= =?UTF-8?q?=D0=B0=D0=BD=D0=B8=D1=8F=20=D1=82=D1=80=D0=B5=D0=BA=D0=B0.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dbScripts/ownradio_db_v3.sql | 167 +++++--- v3/src/main/java/ownradio/domain/Ratio.java | 33 ++ .../ownradio/repository/RatioRepository.java | 18 + .../java/ownradio/service/RatioService.java | 16 + .../service/impl/HistoryServiceImpl.java | 15 +- .../service/impl/RatioServiceImpl.java | 36 ++ .../service/impl/TrackServiceImpl.java | 14 +- .../web/rest/v3/HistoryController.java | 10 +- .../ownradio/web/rest/v3/TrackController.java | 10 +- .../main/resources/data/postgresql/schema.sql | 376 ++++++++++++++++-- 10 files changed, 574 insertions(+), 121 deletions(-) create mode 100644 v3/src/main/java/ownradio/domain/Ratio.java create mode 100644 v3/src/main/java/ownradio/repository/RatioRepository.java create mode 100644 v3/src/main/java/ownradio/service/RatioService.java create mode 100644 v3/src/main/java/ownradio/service/impl/RatioServiceImpl.java diff --git a/dbScripts/ownradio_db_v3.sql b/dbScripts/ownradio_db_v3.sql index 4ab62aa..d03818a 100644 --- a/dbScripts/ownradio_db_v3.sql +++ b/dbScripts/ownradio_db_v3.sql @@ -275,7 +275,8 @@ BEGIN INSERT INTO users (recid, recname, reccreated) SELECT i_userid, 'New user recname', - now(); + now() + WHERE NOT EXISTS(SELECT recid FROM users WHERE recid = i_userid); -- Добавляем новое устройство INSERT INTO devices (recid, userid, recname, reccreated) SELECT @@ -326,7 +327,8 @@ DECLARE INSERT INTO users (recid, recname, reccreated) SELECT i_userid, 'New user recname', - now(); + now() + WHERE NOT EXISTS(SELECT recid FROM users WHERE recid = i_userid); -- Добавляем новое устройство INSERT INTO devices (recid, userid, recname, reccreated) SELECT @@ -868,7 +870,8 @@ BEGIN INSERT INTO users (recid, recname, reccreated) SELECT i_userid, 'New user recname', - now(); + now() + WHERE NOT EXISTS(SELECT recid FROM users WHERE recid = i_userid); -- Добавляем новое устройство INSERT INTO devices (recid, userid, recname, reccreated) SELECT @@ -933,82 +936,122 @@ ALTER FUNCTION public.selectdownloadhistory(uuid) -- DROP FUNCTION public.calculateratios(); CREATE OR REPLACE FUNCTION public.calculateratios() - RETURNS void AS + RETURNS boolean AS $BODY$ -- Функция рассчитывает таблицу коэффициентов схожести интересов для пар пользователей DECLARE - user1 uuid; - user2 uuid; - usercount integer = (SELECT COUNT(*) FROM users); - arrusers uuid ARRAY = (SELECT ARRAY (SELECT recid FROM users)); + -- объявляем курсор и запрос для него + curs1 CURSOR FOR SELECT * FROM( + -- рассчитываем матрицу коэффициентов схожести интересов для каждой пары пользователей + SELECT r.userid as userid01, r2.userid as userid02, SUM(r.ratingsum * r2.ratingsum) as s + FROM ratings r + INNER JOIN ratings r2 ON r.trackid = r2.trackid + AND r.userid != r2.userid + GROUP BY r.userid, r2.userid + ) AS cursor1; + cuser1 uuid; + cuser2 uuid; + cratio integer; BEGIN + DROP TABLE IF EXISTS temp_ratio; + CREATE TEMP TABLE temp_ratio(userid1 uuid, userid2 uuid, ratio integer); -FOR i IN 1..usercount - 1 LOOP - FOR j IN i+1..usercount LOOP - user1 = arrusers[i]; - user2 = arrusers[j]; + OPEN curs1; -- открываем курсор + LOOP -- в цикле проходим по строкам результата запроса курсора + FETCH curs1 INTO cuser1, cuser2, cratio; + IF NOT FOUND THEN EXIT; -- если данных нет - выходим + END IF; + -- если для данной пары пользователей уже записан коэффициент - пропускаем, иначе - записываем во временную таблицу + IF NOT EXISTS (SELECT * FROM temp_ratio WHERE userid1 = cuser2 AND userid2 = cuser1) THEN + INSERT INTO temp_ratio(userid1, userid2, ratio) + VALUES (cuser1, cuser2, cratio); + END IF; + END LOOP; + CLOSE curs1; -- закрываем курсор + + -- обновляем имеющиеся коэффициенты в таблице ratios + UPDATE ratios SET ratio = temp_ratio.ratio FROM temp_ratio + WHERE (ratios.userid1 = temp_ratio.userid1 AND ratios.userid2 = temp_ratio.userid2) + OR (ratios.userid1 = temp_ratio.userid2 AND ratios.userid2 = temp_ratio.userid1); + + -- если в ratios меньше пар пользователей, чем во временной таблице - вставляем недостающие записи + IF (SELECT COUNT(*) FROM ratios) < (SELECT COUNT(*) FROM temp_ratio) THEN + INSERT INTO ratios (userid1, userid2, ratio) + (SELECT tr.userid1, tr.userid2, tr.ratio FROM temp_ratio AS tr + LEFT OUTER JOIN ratios AS rr ON tr.userid1 = rr.userid1 AND tr.userid2 = rr.userid2 + WHERE rr.userid1 IS NULL OR rr.userid2 IS NULL + ); + END IF; + RETURN TRUE; +END; - UPDATE ratios set ratio = - (SELECT SUM(ratio.ratingsum1 * ratio.ratingsum2) FROM ( - SELECT DISTINCT recid, +$BODY$ + LANGUAGE plpgsql VOLATILE + COST 100; +ALTER FUNCTION public.calculateratios() + OWNER TO postgres; - (SELECT ratingsum - FROM ratings - WHERE (trackid = t.recid) - AND (userid = user1)) AS ratingsum1, + +-- Function: public.updateratios(uuid) - (SELECT ratingsum - FROM ratings - WHERE (trackid = t.recid) - AND (userid = user2)) AS ratingsum2 +-- DROP FUNCTION public.updateratios(uuid); - FROM tracks AS t - WHERE recid IN (SELECT trackid - FROM ratings - WHERE trackid IN (SELECT trackid FROM ratings WHERE userid = user1) - AND userid = user2) - ) AS ratio) +CREATE OR REPLACE FUNCTION public.updateratios(i_userid uuid) + RETURNS boolean AS +$BODY$ - WHERE (userid1 = user1 AND userid2 = user2) - OR (userid1 = user2 AND userid2 = user1); + -- Функция обновляет таблицу коэффициентов схожести интересов для выбранного пользователя +DECLARE + -- объявляем курсор и запрос для него + curs1 CURSOR FOR SELECT * FROM( + -- рассчитываем матрицу коэффициентов схожести интересов для каждой пары пользователей + SELECT r.userid as userid01, r2.userid as userid02, SUM(r.ratingsum * r2.ratingsum) as s + FROM ratings r + INNER JOIN ratings r2 ON r.trackid = r2.trackid + AND r.userid != r2.userid + AND (r.userid = i_userid OR r2.userid = i_userid) + GROUP BY r.userid, r2.userid + ) AS cursor1; + cuser1 uuid; + cuser2 uuid; + cratio integer; +BEGIN + DROP TABLE IF EXISTS temp_ratio; + CREATE TEMP TABLE temp_ratio(userid1 uuid, userid2 uuid, ratio integer); - -- Если таблица была обновлена - выход из функции, иначе - добавим запись - IF found THEN - CONTINUE; - END IF; - - INSERT INTO public.ratios( - userid1, userid2, ratio) - VALUES(user1, user2, - (SELECT SUM(ratio.ratingsum1 * ratio.ratingsum2) FROM ( - SELECT DISTINCT recid, - - (SELECT ratingsum - FROM ratings - WHERE (trackid = t.recid) - AND (userid = user1)) AS ratingsum1, - - (SELECT ratingsum - FROM ratings - WHERE (trackid = t.recid) - AND (userid = user2)) AS ratingsum2 - - FROM tracks AS t - WHERE recid IN (SELECT trackid - FROM ratings - WHERE trackid IN (SELECT trackid FROM ratings WHERE userid = user1) - AND userid = user2) - ) AS ratio)); + OPEN curs1; -- открываем курсор + LOOP -- в цикле проходим по строкам результата запроса курсора + FETCH curs1 INTO cuser1, cuser2, cratio; + IF NOT FOUND THEN EXIT; -- если данных нет - выходим + END IF; + -- если для данной пары пользователей уже записан коэффициент - пропускаем, иначе - записываем во временную таблицу + IF NOT EXISTS (SELECT * FROM temp_ratio WHERE userid1 = cuser2 AND userid2 = cuser1) THEN + INSERT INTO temp_ratio(userid1, userid2, ratio) + VALUES (cuser1, cuser2, cratio); + END IF; END LOOP; -END LOOP; - + CLOSE curs1; -- закрываем курсор + + -- обновляем имеющиеся коэффициенты в таблице ratios + UPDATE ratios SET ratio = temp_ratio.ratio FROM temp_ratio + WHERE (ratios.userid1 = temp_ratio.userid1 AND ratios.userid2 = temp_ratio.userid2) + OR (ratios.userid1 = temp_ratio.userid2 AND ratios.userid2 = temp_ratio.userid1); + + -- если в ratios меньше пар пользователей, чем во временной таблице - вставляем недостающие записи + IF (SELECT COUNT(*) FROM ratios WHERE userid1 = i_userid or userid2 = i_userid) < (SELECT COUNT(*) FROM temp_ratio) THEN + INSERT INTO ratios (userid1, userid2, ratio) + (SELECT tr.userid1, tr.userid2, tr.ratio FROM temp_ratio AS tr + LEFT OUTER JOIN ratios AS rr ON tr.userid1 = rr.userid1 AND tr.userid2 = rr.userid2 + WHERE rr.userid1 IS NULL OR rr.userid2 IS NULL + ); + END IF; + RETURN TRUE; END; + $BODY$ LANGUAGE plpgsql VOLATILE COST 100; ALTER FUNCTION public.calculateratios() OWNER TO postgres; - - diff --git a/v3/src/main/java/ownradio/domain/Ratio.java b/v3/src/main/java/ownradio/domain/Ratio.java new file mode 100644 index 0000000..1a8c652 --- /dev/null +++ b/v3/src/main/java/ownradio/domain/Ratio.java @@ -0,0 +1,33 @@ +package ownradio.domain; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import javax.persistence.*; + +/** + * Сущность для хранения коэффициентов схожести интересов + * + * Created by a.polunina on 16.02.2017. + */ +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@Entity +@Table(name = "ratios") +public class Ratio extends AbstractEntity { +// @ManyToOne + @JoinColumn(name = "userid") + @Column(nullable = false) + private User userid1; + +// @ManyToOne + @JoinColumn(name = "userid") + @Column(nullable = false) + private User userid2; + + private Integer ratio; +} diff --git a/v3/src/main/java/ownradio/repository/RatioRepository.java b/v3/src/main/java/ownradio/repository/RatioRepository.java new file mode 100644 index 0000000..4232cdc --- /dev/null +++ b/v3/src/main/java/ownradio/repository/RatioRepository.java @@ -0,0 +1,18 @@ +package ownradio.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import ownradio.domain.Ratio; + +import java.util.UUID; + +/** + * Интерфейс репозитория, для хранения коэффициентов схожести интересов + * + * Created by a.polunina on 16.02.2017. + */ +public interface RatioRepository extends JpaRepository { + @Query(value = "select updateratios(?1)", nativeQuery = true) + boolean updateRatios(UUID deviceId); + +} \ No newline at end of file diff --git a/v3/src/main/java/ownradio/service/RatioService.java b/v3/src/main/java/ownradio/service/RatioService.java new file mode 100644 index 0000000..1581c63 --- /dev/null +++ b/v3/src/main/java/ownradio/service/RatioService.java @@ -0,0 +1,16 @@ +package ownradio.service; + +import ownradio.domain.Ratio; + +import java.util.UUID; + +/** + * Интерфейс сервиса, для работы с коэффициентами схожести интересов пользователей + * + * Created by a.polunina on 16.02.2017. + */ +public interface RatioService { + void save (Ratio ratio); + + void updateRatios(UUID deviceId); +} diff --git a/v3/src/main/java/ownradio/service/impl/HistoryServiceImpl.java b/v3/src/main/java/ownradio/service/impl/HistoryServiceImpl.java index 32eff85..bceef04 100644 --- a/v3/src/main/java/ownradio/service/impl/HistoryServiceImpl.java +++ b/v3/src/main/java/ownradio/service/impl/HistoryServiceImpl.java @@ -1,14 +1,17 @@ package ownradio.service.impl; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.SqlParameter; +import org.springframework.jdbc.core.simple.SimpleJdbcCall; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import ownradio.domain.DownloadTrack; import ownradio.domain.History; import ownradio.domain.Rating; import ownradio.repository.DownloadTrackRepository; import ownradio.repository.HistoryRepository; import ownradio.repository.RatingRepository; +import ownradio.repository.RatioRepository; import ownradio.service.HistoryService; @Service @@ -16,12 +19,14 @@ public class HistoryServiceImpl implements HistoryService { private final HistoryRepository historyRepository; private final RatingRepository ratingRepository; + private final RatioRepository ratioRepository; private final DownloadTrackRepository downloadTrackRepository; @Autowired - public HistoryServiceImpl(HistoryRepository historyRepository, RatingRepository ratingRepository, DownloadTrackRepository downloadTrackRepository) { + public HistoryServiceImpl(HistoryRepository historyRepository, RatingRepository ratingRepository, RatioRepository ratioRepository, DownloadTrackRepository downloadTrackRepository) { this.historyRepository = historyRepository; this.ratingRepository = ratingRepository; + this.ratioRepository = ratioRepository; this.downloadTrackRepository = downloadTrackRepository; } @@ -45,5 +50,11 @@ public void save(History history) { rating.setRatingsum(history.getIsListen()); ratingRepository.saveAndFlush(rating); } + + try { + ratioRepository.updateRatios(history.getDevice().getRecid()); + }catch (Exception ex){ + ex.printStackTrace(); + } } } diff --git a/v3/src/main/java/ownradio/service/impl/RatioServiceImpl.java b/v3/src/main/java/ownradio/service/impl/RatioServiceImpl.java new file mode 100644 index 0000000..fce00a3 --- /dev/null +++ b/v3/src/main/java/ownradio/service/impl/RatioServiceImpl.java @@ -0,0 +1,36 @@ +package ownradio.service.impl; + +import org.springframework.beans.factory.annotation.Autowired; +import ownradio.domain.Ratio; +import ownradio.repository.RatioRepository; +import ownradio.service.RatioService; + +import java.util.UUID; + +/** + * Created by a.polunina on 16.02.2017. + */ +public class RatioServiceImpl implements RatioService{ + + private final RatioRepository ratioRepository; + + @Autowired + public RatioServiceImpl(RatioRepository ratioRepository) { + this.ratioRepository = ratioRepository; + } + + @Override + public void save(Ratio ratio) { + ratioRepository.saveAndFlush(ratio); + } + + @Override + public void updateRatios(UUID deviceId){ + try { + ratioRepository.updateRatios(deviceId); + }catch (Exception ex){ + ex.printStackTrace(); + } + + } +} diff --git a/v3/src/main/java/ownradio/service/impl/TrackServiceImpl.java b/v3/src/main/java/ownradio/service/impl/TrackServiceImpl.java index 8999058..011068d 100644 --- a/v3/src/main/java/ownradio/service/impl/TrackServiceImpl.java +++ b/v3/src/main/java/ownradio/service/impl/TrackServiceImpl.java @@ -45,15 +45,11 @@ public UUID getNextTrackId(UUID deviceId) { public NextTrack getNextTrackIdV2(UUID deviceId) { NextTrack nextTrack = new NextTrack(); List objects = trackRepository.getNextTrackV2(deviceId); - try{ - if(objects != null) { - nextTrack.setTrackid(UUID.fromString((String) objects.get(0)[0])); - nextTrack.setMethodid((Integer) objects.get(0)[1]); - return nextTrack; - }else{ - return null; - } - }catch (Exception ex){ + if(objects != null) { + nextTrack.setTrackid(UUID.fromString((String) objects.get(0)[0])); + nextTrack.setMethodid((Integer) objects.get(0)[1]); + return nextTrack; + }else{ return null; } } diff --git a/v3/src/main/java/ownradio/web/rest/v3/HistoryController.java b/v3/src/main/java/ownradio/web/rest/v3/HistoryController.java index 057fa9d..bf7970f 100644 --- a/v3/src/main/java/ownradio/web/rest/v3/HistoryController.java +++ b/v3/src/main/java/ownradio/web/rest/v3/HistoryController.java @@ -3,7 +3,6 @@ import lombok.Data; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.format.annotation.DateTimeFormat; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; @@ -16,7 +15,6 @@ import java.text.SimpleDateFormat; import java.util.Calendar; -import java.util.Date; import java.util.UUID; /** @@ -64,7 +62,7 @@ public History getHistory() { } } - //formData + //form-data @RequestMapping(value = "/{deviceId}/{trackId}", method = RequestMethod.POST) public ResponseEntity save(@PathVariable UUID deviceId, @PathVariable UUID trackId, HistoryDTO historyDTO) { return getResponseEntity(deviceId, trackId, historyDTO.getHistory()); @@ -78,8 +76,8 @@ public ResponseEntity save2(@PathVariable UUID deviceId, @PathVariable UUID trac private ResponseEntity getResponseEntity(@PathVariable UUID deviceId, @PathVariable UUID trackId, @RequestBody History history) { try { - log.info("{} {}",deviceId.toString(),trackId.toString()); - log.info("{} {} {}",history.getLastListen(), history.getIsListen(), history.getMethod()); + log.info("deviceId:{} trackId: {}",deviceId.toString(),trackId.toString()); + log.info("{} {} {}",history.getLastListen(), history.getIsListen(), history.getMethodid()); Track track = trackService.getById(trackId); Device device = deviceService.getById(deviceId); @@ -91,7 +89,7 @@ private ResponseEntity getResponseEntity(@PathVariable UUID deviceId, @PathVaria history.setDevice(device); historyService.save(history); - + log.info("Save history, rating and update ratios"); return new ResponseEntity(HttpStatus.OK); } catch (Exception e) { return new ResponseEntity(HttpStatus.INTERNAL_SERVER_ERROR); diff --git a/v3/src/main/java/ownradio/web/rest/v3/TrackController.java b/v3/src/main/java/ownradio/web/rest/v3/TrackController.java index be8696c..515dcc4 100644 --- a/v3/src/main/java/ownradio/web/rest/v3/TrackController.java +++ b/v3/src/main/java/ownradio/web/rest/v3/TrackController.java @@ -97,7 +97,13 @@ private HttpHeaders getHttpAudioHeaders() { @RequestMapping(value = "/{deviceId}/next", method = RequestMethod.GET) public ResponseEntity getNextTrack(@PathVariable UUID deviceId) { - NextTrack nextTrack = trackService.getNextTrackIdV2(deviceId); + NextTrack nextTrack = null; + try { + nextTrack = trackService.getNextTrackIdV2(deviceId); + }catch (Exception ex){ + + } + Map trackInfo = new HashMap<>(); if (nextTrack != null) { try { @@ -138,7 +144,7 @@ public ResponseEntity getNextTrack(@PathVariable UUID deviceId) { downloadTrack.setTrack(track); downloadTrack.setDevice(device); downloadTrackRepository.saveAndFlush(downloadTrack); - + log.info("getNextTrack return {}", trackInfo.get("id")); return new ResponseEntity<>(trackInfo, HttpStatus.OK); }catch (Exception ex){ log.info("{}", ex.getMessage()); diff --git a/v3/src/main/resources/data/postgresql/schema.sql b/v3/src/main/resources/data/postgresql/schema.sql index 4166698..435d573 100644 --- a/v3/src/main/resources/data/postgresql/schema.sql +++ b/v3/src/main/resources/data/postgresql/schema.sql @@ -13,16 +13,17 @@ BEGIN -- Добавляем нового пользователя INSERT INTO users (recid, recname, reccreated) SELECT - i_userid, - ''New user recname'', - now(); + i_userid, + ''New user recname'', + now() + WHERE NOT EXISTS(SELECT recid FROM users WHERE recid = i_userid); -- Добавляем новое устройство INSERT INTO devices (recid, userid, recname, reccreated) SELECT - i_deviceid, - i_userid, - ''New device recname'', - now(); + i_deviceid, + i_userid, + ''New device recname'', + now(); ELSE SELECT (SELECT userid FROM devices @@ -88,16 +89,17 @@ BEGIN -- Добавляем нового пользователя INSERT INTO users (recid, recname, reccreated) SELECT - i_userid, - ''New user recname'', - now(); + i_userid, + ''New user recname'', + now() + WHERE NOT EXISTS(SELECT recid FROM users WHERE recid = i_userid); -- Добавляем новое устройство INSERT INTO devices (recid, userid, recname, reccreated) SELECT - i_deviceid, - i_userid, - ''New device recname'', - now(); + i_deviceid, + i_userid, + ''New device recname'', + now(); ELSE SELECT (SELECT userid FROM devices @@ -209,12 +211,12 @@ BEGIN -- Определяем количество "своих" треков пользователя, ограничивая его 900 owntracks = (SELECT COUNT(*) - FROM ( - SELECT * - FROM ratings - WHERE userid = i_userid - AND ratingsum >= 0 - LIMIT 900) AS count); + FROM ( + SELECT * + FROM ratings + WHERE userid = i_userid + AND ratingsum >= 0 + LIMIT 900) AS count); -- Если rnd меньше количества "своих" треков, выбираем трек из треков пользователя (добавленных им или прослушанных до конца) -- с положительным рейтингом, за исключением прослушанных за последние сутки @@ -290,12 +292,12 @@ BEGIN -- Определяем количество "своих" треков пользователя, ограничивая его 900 owntracks = (SELECT COUNT(*) - FROM ( - SELECT * - FROM ratings - WHERE userid = i_userid - AND ratingsum >= 0 - LIMIT 900) AS count); + FROM ( + SELECT * + FROM ratings + WHERE userid = i_userid + AND ratingsum >= 0 + LIMIT 900) AS count); -- Если rnd меньше количества "своих" треков, выбираем трек из треков пользователя (добавленных им или прослушанных до конца) -- с положительным рейтингом, за исключением прослушанных за последние сутки @@ -327,8 +329,8 @@ BEGIN FROM tracks WHERE recid = trackid) != 0) AND trackid NOT IN (SELECT trackid - FROM downloadtracks - WHERE reccreated < localtimestamp - INTERVAL ''1 day'') + FROM downloadtracks + WHERE reccreated > localtimestamp - INTERVAL ''1 day'') ORDER BY RANDOM() LIMIT 1; @@ -353,8 +355,8 @@ BEGIN AND (iscensorial IS NULL OR iscensorial != 0) AND (length > 120 OR length IS NULL) AND recid NOT IN (SELECT trackid - FROM downloadtracks - WHERE reccreated < localtimestamp - INTERVAL ''1 day'') + FROM downloadtracks + WHERE reccreated > localtimestamp - INTERVAL ''1 day'') ORDER BY RANDOM() LIMIT 1; @@ -393,22 +395,23 @@ BEGIN -- Добавляем устройство, если его еще не существует -- Если ID устройства еще нет в БД IF NOT EXISTS(SELECT recid - FROM devices - WHERE recid = i_deviceid) + FROM devices + WHERE recid = i_deviceid) THEN -- Добавляем нового пользователя INSERT INTO users (recid, recname, reccreated) SELECT - i_userid, - ''New user recname'', - now(); + i_userid, + ''New user recname'', + now() + WHERE NOT EXISTS(SELECT recid FROM users WHERE recid = i_userid); -- Добавляем новое устройство INSERT INTO devices (recid, userid, recname, reccreated) SELECT - i_deviceid, - i_userid, - ''New device recname'', - now(); + i_deviceid, + i_userid, + ''New device recname'', + now(); ELSE SELECT (SELECT userid FROM devices @@ -421,7 +424,300 @@ BEGIN RETURN QUERY SELECT CAST((nexttrack.track) AS CHARACTER VARYING), nexttrack.methodid - FROM getnexttrackid_v3(i_deviceid) AS nexttrack; + FROM getnexttrackid_v6(i_deviceid) AS nexttrack; +END; +' +LANGUAGE plpgsql; + + +CREATE OR REPLACE FUNCTION calculateratios() + RETURNS boolean AS +' +-- Функция рассчитывает таблицу коэффициентов схожести интересов для пар пользователей +DECLARE + -- объявляем курсор и запрос для него + curs1 CURSOR FOR SELECT * FROM( + -- рассчитываем матрицу коэффициентов схожести интересов для каждой пары пользователей + SELECT r.userid as userid01, r2.userid as userid02, SUM(r.ratingsum * r2.ratingsum) as s + FROM ratings r + INNER JOIN ratings r2 ON r.trackid = r2.trackid + AND r.userid != r2.userid + GROUP BY r.userid, r2.userid + ) AS cursor1; + cuser1 uuid; + cuser2 uuid; + cratio integer; +BEGIN + DROP TABLE IF EXISTS temp_ratio; + CREATE TEMP TABLE temp_ratio(userid1 uuid, userid2 uuid, ratio integer); + + OPEN curs1; -- открываем курсор + LOOP -- в цикле проходим по строкам результата запроса курсора + FETCH curs1 INTO cuser1, cuser2, cratio; + + IF NOT FOUND THEN EXIT; -- если данных нет - выходим + END IF; + -- если для данной пары пользователей уже записан коэффициент - пропускаем, иначе - записываем во временную таблицу + IF NOT EXISTS (SELECT * FROM temp_ratio WHERE userid1 = cuser2 AND userid2 = cuser1) THEN + INSERT INTO temp_ratio(userid1, userid2, ratio) + VALUES (cuser1, cuser2, cratio); + END IF; + END LOOP; + CLOSE curs1; -- закрываем курсор + + -- обновляем имеющиеся коэффициенты в таблице ratios + UPDATE ratios SET ratio = temp_ratio.ratio FROM temp_ratio + WHERE (ratios.userid1 = temp_ratio.userid1 AND ratios.userid2 = temp_ratio.userid2) + OR (ratios.userid1 = temp_ratio.userid2 AND ratios.userid2 = temp_ratio.userid1); + + -- если в ratios меньше пар пользователей, чем во временной таблице - вставляем недостающие записи + IF (SELECT COUNT(*) FROM ratios) < (SELECT COUNT(*) FROM temp_ratio) THEN + INSERT INTO ratios (userid1, userid2, ratio) + (SELECT tr.userid1, tr.userid2, tr.ratio FROM temp_ratio AS tr + LEFT OUTER JOIN ratios AS rr ON tr.userid1 = rr.userid1 AND tr.userid2 = rr.userid2 + WHERE rr.userid1 IS NULL OR rr.userid2 IS NULL + ); + END IF; + RETURN TRUE; +END; +' +LANGUAGE plpgsql; + + +CREATE OR REPLACE FUNCTION updateratios(i_userid uuid) + RETURNS boolean AS +' +-- Функция обновляет таблицу коэффициентов схожести интересов для выбранного пользователя +DECLARE + -- объявляем курсор и запрос для него + curs1 CURSOR FOR SELECT * FROM( + -- рассчитываем матрицу коэффициентов схожести интересов для каждой пары пользователей + SELECT r.userid as userid01, r2.userid as userid02, SUM(r.ratingsum * r2.ratingsum) as s + FROM ratings r + INNER JOIN ratings r2 ON r.trackid = r2.trackid + AND r.userid != r2.userid + AND (r.userid = i_userid OR r2.userid = i_userid) + GROUP BY r.userid, r2.userid + ) AS cursor1; + cuser1 uuid; + cuser2 uuid; + cratio integer; +BEGIN + DROP TABLE IF EXISTS temp_ratio; + CREATE TEMP TABLE temp_ratio(userid1 uuid, userid2 uuid, ratio integer); + + OPEN curs1; -- открываем курсор + LOOP -- в цикле проходим по строкам результата запроса курсора + FETCH curs1 INTO cuser1, cuser2, cratio; + + IF NOT FOUND THEN EXIT; -- если данных нет - выходим + END IF; + -- если для данной пары пользователей уже записан коэффициент - пропускаем, иначе - записываем во временную таблицу + IF NOT EXISTS (SELECT * FROM temp_ratio WHERE userid1 = cuser2 AND userid2 = cuser1) THEN + INSERT INTO temp_ratio(userid1, userid2, ratio) + VALUES (cuser1, cuser2, cratio); + END IF; + END LOOP; + CLOSE curs1; -- закрываем курсор + + -- обновляем имеющиеся коэффициенты в таблице ratios + UPDATE ratios SET ratio = temp_ratio.ratio FROM temp_ratio + WHERE (ratios.userid1 = temp_ratio.userid1 AND ratios.userid2 = temp_ratio.userid2) + OR (ratios.userid1 = temp_ratio.userid2 AND ratios.userid2 = temp_ratio.userid1); + + -- если в ratios меньше пар пользователей, чем во временной таблице - вставляем недостающие записи + IF (SELECT COUNT(*) FROM ratios WHERE userid1 = i_userid or userid2 = i_userid) < (SELECT COUNT(*) FROM temp_ratio) THEN + INSERT INTO ratios (userid1, userid2, ratio) + (SELECT tr.userid1, tr.userid2, tr.ratio FROM temp_ratio AS tr + LEFT OUTER JOIN ratios AS rr ON tr.userid1 = rr.userid1 AND tr.userid2 = rr.userid2 + WHERE rr.userid1 IS NULL OR rr.userid2 IS NULL + ); + END IF; +RETURN TRUE; END; ' LANGUAGE plpgsql; + + +CREATE OR REPLACE FUNCTION public.getnexttrackid_v6(IN i_deviceid uuid) + RETURNS TABLE(track uuid, methodid integer) AS +' +DECLARE + i_userid UUID = i_deviceid; + rnd INTEGER = (SELECT trunc(random() * 1001)); + o_methodid INTEGER; -- id метода выбора трека + owntracks INTEGER; -- количество "своих" треков пользователя (обрезаем на 900 шт) + arrusers uuid ARRAY; -- массив пользователей для i_userid с неотрицательнымм коэффициентами схожести интересов +BEGIN + -- Выбираем следующий трек + + -- Определяем количество "своих" треков пользователя, ограничивая его 900 + owntracks = (SELECT COUNT(*) + FROM ( + SELECT * + FROM ratings + WHERE userid = i_userid + AND ratingsum >= 0 + LIMIT 900) AS count); + + -- Если rnd меньше количества "своих" треков, выбираем трек из треков пользователя (добавленных им или прослушанных до конца) + -- с положительным рейтингом, за исключением прослушанных за последние сутки + + IF (rnd < owntracks) + THEN + o_methodid = 2; -- метод выбора из своих треков + RETURN QUERY + SELECT + trackid, + o_methodid + FROM ratings + WHERE userid = i_userid + AND lastlisten < localtimestamp - INTERVAL ''1 day'' + AND ratingsum >= 0 + AND (SELECT isexist + FROM tracks + WHERE recid = trackid) = 1 + AND ((SELECT length + FROM tracks + WHERE recid = trackid) >= 120 + OR (SELECT length + FROM tracks + WHERE recid = trackid) IS NULL) + AND ((SELECT iscensorial + FROM tracks + WHERE recid = trackid) IS NULL + OR (SELECT iscensorial + FROM tracks + WHERE recid = trackid) != 0) + AND trackid NOT IN (SELECT trackid + FROM downloadtracks + WHERE reccreated > localtimestamp - INTERVAL ''1 day'') + ORDER BY RANDOM() + LIMIT 1; + + -- Если такой трек найден - выход из функции, возврат найденного значения + IF FOUND + THEN RETURN; + END IF; + END IF; + + -- Если rnd больше количества "своих" треков - рекомендуем трек из треков пользователя с наибольшим + -- коэффициентом схожести интересов и наибольшим рейтингом прослушивания + + -- Выберем всех пользователей с неотрицательным коэффициентом схожести интересов для i_userid + -- отсортировав по убыванию коэффициентов + arrusers = (SELECT ARRAY (SELECT CASE WHEN userid1 = i_userid THEN userid2 + WHEN userid2 = i_userid THEN userid1 + ELSE NULL + END + FROM ratios + WHERE userid1 = i_userid OR userid2 = i_userid + AND ratio >= 0 + ORDER BY ratio DESC + )); + -- Выбираем пользователя i, с которым у него максимальный коэффициент. Среди его треков ищем трек + -- с максимальным рейтингом прослушивания, за исключением уже прослушанных пользователем i_userid. + -- Если рекомендовать нечего - берем следующего пользователя с наибольшим коэффициентом из оставшихся. + FOR i IN 1.. (SELECT COUNT (*) FROM unnest(arrusers)) LOOP + o_methodid = 4; -- метод выбора из рекомендованных треков + RETURN QUERY + SELECT + trackid, + o_methodid + FROM ratings + WHERE userid = arrusers[i] + AND ratingsum > 0 + AND trackid NOT IN (SELECT trackid FROM ratings WHERE userid = i_userid) + AND trackid NOT IN (SELECT trackid + FROM downloadtracks + WHERE deviceid = i_userid + AND reccreated > localtimestamp - INTERVAL ''1 day'') + AND (SELECT isexist + FROM tracks + WHERE recid = trackid) = 1 + AND ((SELECT length + FROM tracks + WHERE recid = trackid) >= 120 + OR (SELECT length + FROM tracks + WHERE recid = trackid) IS NULL) + AND ((SELECT iscensorial + FROM tracks + WHERE recid = trackid) IS NULL + OR (SELECT iscensorial + FROM tracks + WHERE recid = trackid) != 0) + ORDER BY ratingsum DESC + LIMIT 1; + -- Если нашли что рекомендовать - выходим из функции + IF found THEN + RETURN; + END IF; + END LOOP; + + -- При отсутствии рекомендаций, выдавать случайный трек из непрослушанных треков с неотрицательным + -- рейтингом среди пользователей со схожим вкусом. + FOR i IN 1.. (SELECT COUNT (*) FROM unnest(arrusers)) LOOP + o_methodid = 5; -- метод выбора из непрослушанных треков с неотрицательным рейтингом среди пользователей со схожим вкусом + RETURN QUERY + SELECT + recid, + o_methodid + FROM tracks + WHERE recid NOT IN (SELECT trackid FROM ratings WHERE userid = arrusers[i] AND ratingsum < 0) + AND recid NOT IN (SELECT trackid FROM ratings WHERE userid = i_userid) + AND isexist = 1 + AND (iscensorial IS NULL OR iscensorial != 0) + AND (length > 120 OR length IS NULL) + AND recid NOT IN (SELECT trackid + FROM downloadtracks + WHERE reccreated > localtimestamp - INTERVAL ''1 day'') + ORDER BY RANDOM() + LIMIT 1; + -- Если нашли что рекомендовать - выходим из функции + IF found THEN + RETURN; + END IF; + END LOOP; + + -- Если таких треков нет - выбираем случайный трек из ни разу не прослушанных пользователем треков + o_methodid = 3; -- метод выбора из непрослушанных треков + RETURN QUERY + SELECT + recid, + o_methodid + FROM tracks + WHERE recid NOT IN + (SELECT trackid + FROM ratings + WHERE userid = i_userid) + AND isexist = 1 + AND (iscensorial IS NULL OR iscensorial != 0) + AND (length > 120 OR length IS NULL) + AND recid NOT IN (SELECT trackid + FROM downloadtracks + WHERE reccreated > localtimestamp - INTERVAL ''1 day'') + ORDER BY RANDOM() + LIMIT 1; + + -- Если такой трек найден - выход из функции, возврат найденного значения + IF FOUND + THEN RETURN; + END IF; + + -- Если предыдущие запросы вернули null, выбираем случайный трек + o_methodid = 1; -- метод выбора случайного трека + RETURN QUERY + SELECT + recid, + o_methodid + FROM tracks + WHERE isexist = 1 + AND (iscensorial IS NULL OR iscensorial != 0) + AND (length > 120 OR length IS NULL) + ORDER BY RANDOM() + LIMIT 1; + RETURN; +END; +' +LANGUAGE plpgsql; \ No newline at end of file From 855b19b2c951e72ba744591d816085ef39cd6b19 Mon Sep 17 00:00:00 2001 From: Alexandra Polunina Date: Fri, 3 Mar 2017 17:36:55 +0300 Subject: [PATCH 26/44] =?UTF-8?q?=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=B8=D0=BB=D0=B0=20=D0=BF=D1=80=D0=BE=D0=B2=D0=B5=D1=80=D0=BA?= =?UTF-8?q?=D1=83=20=D0=BF=D1=80=D0=B8=20=D0=B2=D1=8B=D0=B4=D0=B0=D1=87?= =?UTF-8?q?=D0=B5=20=D1=82=D1=80=D0=B5=D0=BA=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dbScripts/ownradio_db_v3.sql | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/dbScripts/ownradio_db_v3.sql b/dbScripts/ownradio_db_v3.sql index d03818a..03dbfae 100644 --- a/dbScripts/ownradio_db_v3.sql +++ b/dbScripts/ownradio_db_v3.sql @@ -581,7 +581,7 @@ BEGIN WHERE recid = trackid) != 0) AND trackid NOT IN (SELECT trackid FROM downloadtracks - WHERE reccreated < localtimestamp - INTERVAL '1 day') + WHERE reccreated > localtimestamp - INTERVAL '1 day') ORDER BY RANDOM() LIMIT 1; @@ -607,7 +607,7 @@ BEGIN AND (length > 120 OR length IS NULL) AND recid NOT IN (SELECT trackid FROM downloadtracks - WHERE reccreated < localtimestamp - INTERVAL '1 day') + WHERE reccreated > localtimestamp - INTERVAL '1 day') ORDER BY RANDOM() LIMIT 1; @@ -695,7 +695,7 @@ BEGIN WHERE recid = trackid) != 0) AND trackid NOT IN (SELECT trackid FROM downloadtracks - WHERE reccreated < localtimestamp - INTERVAL '1 day') + WHERE reccreated > localtimestamp - INTERVAL '1 day') ORDER BY RANDOM() LIMIT 1; @@ -734,8 +734,8 @@ BEGIN AND trackid NOT IN (SELECT trackid FROM ratings WHERE userid = i_userid) AND trackid NOT IN (SELECT trackid FROM downloadtracks - WHERE userid = i_userid - AND reccreated < localtimestamp - INTERVAL '1 day') + WHERE deviceid = i_deviceid + AND reccreated > localtimestamp - INTERVAL '1 day') AND (SELECT isexist FROM tracks WHERE recid = trackid) = 1 @@ -775,7 +775,7 @@ BEGIN AND (length > 120 OR length IS NULL) AND recid NOT IN (SELECT trackid FROM downloadtracks - WHERE reccreated < localtimestamp - INTERVAL '1 day') + WHERE reccreated > localtimestamp - INTERVAL '1 day') ORDER BY RANDOM() LIMIT 1; -- Если нашли что рекомендовать - выходим из функции @@ -800,7 +800,7 @@ BEGIN AND (length > 120 OR length IS NULL) AND recid NOT IN (SELECT trackid FROM downloadtracks - WHERE reccreated < localtimestamp - INTERVAL '1 day') + WHERE reccreated > localtimestamp - INTERVAL '1 day') ORDER BY RANDOM() LIMIT 1; From 5eddcb48f53ec4286bfc97aaf1aadc78598fdf89 Mon Sep 17 00:00:00 2001 From: Alexandra Polunina Date: Tue, 14 Mar 2017 17:02:15 +0300 Subject: [PATCH 27/44] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8?= =?UTF-8?q?=D0=BB=D0=B0=20getnexttrack=20V7,=20=D1=81=D0=BE=D1=85=D1=80?= =?UTF-8?q?=D0=B0=D0=BD=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=BC=D0=B5=D1=82=D0=BE?= =?UTF-8?q?=D0=B4=D0=B0=20=D0=B2=D1=8B=D0=B4=D0=B0=D1=87=D0=B8=20=D1=82?= =?UTF-8?q?=D1=80=D0=B5=D0=BA=D0=B0=20=D0=B2=20=D1=82=D0=B0=D0=B1=D0=BB?= =?UTF-8?q?=D0=B8=D1=86=D1=83=20downloadtracks=20=D0=BF=D1=80=D0=B8=20?= =?UTF-8?q?=D0=B2=D1=8B=D0=B4=D0=B0=D1=87=D0=B5=20=D1=82=D1=80=D0=B5=D0=BA?= =?UTF-8?q?=D0=B0,=20API=20v4=20=D0=B4=D0=BB=D1=8F=20=D0=B2=D1=8B=D0=B4?= =?UTF-8?q?=D0=B0=D1=87=D0=B8=20=D1=81=D0=BB=D0=B5=D0=B4=D1=83=D1=8E=D1=89?= =?UTF-8?q?=D0=B5=D0=B3=D0=BE=20=D1=82=D1=80=D0=B5=D0=BA=D0=B0=20=D0=B8=20?= =?UTF-8?q?=D0=BE=D1=82=D0=BF=D1=80=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8?= =?UTF-8?q?=D1=8F=20=D0=B8=D1=81=D1=82=D0=BE=D1=80=D0=B8=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 29 +++ dbScripts/ownradio_db_v3.sql | 216 +++++++++++++++++- .../java/ownradio/domain/DownloadTrack.java | 2 + .../web/rest/v2/HistoryController.java | 2 +- .../ownradio/web/rest/v3/TrackController.java | 3 +- .../ownradio/web/v4/HistoryController.java | 99 ++++++++ .../java/ownradio/web/v4/TrackController.java | 161 +++++++++++++ .../main/resources/data/postgresql/schema.sql | 205 ++++++++++++++++- 8 files changed, 711 insertions(+), 6 deletions(-) create mode 100644 v3/src/main/java/ownradio/web/v4/HistoryController.java create mode 100644 v3/src/main/java/ownradio/web/v4/TrackController.java diff --git a/README.md b/README.md index beaba71..ba6e7d3 100644 --- a/README.md +++ b/README.md @@ -67,3 +67,32 @@ Content-Type →application/json;charset=UTF-8 ##### HttpStatus * `200, "OK"` – если все ок * `500, "Internal Server Error"` – если произошел сбой на сервере + + +Web API v4 +--- + +### Получение следующего трека с сервера +##### GET /v4/tracks/{deviceId}/next +* `{deviceId}` – UUID девайса + +##### Response +Content-Type →application/json;charset=UTF-8 +{ + "artist": "Artist", + "length": "duration", + "name": "Title", + "id": "00000000-0000-0000-0000-000000000000" + +### Сохранение истории треков +##### POST /v4/histories/{deviceId}/{trackId} +* `{trackId}` – UUID прослушанного трека +* `{deviceId}` – UUID устройства где был прослушан трек +* `lastListen` - Время последнего прослушивания или пропуска трека для данного пользователя ("yyyy-MM-ddTHH:mm:ss") +* `isListen` - Признак прослушан ли трек до конца: 1 - прослушан, -1 – нет (int) + +##### HttpStatus +* `200, "OK"` – если все ок +* `500, "Internal Server Error"` – если произошел сбой на сервере + +} \ No newline at end of file diff --git a/dbScripts/ownradio_db_v3.sql b/dbScripts/ownradio_db_v3.sql index 03dbfae..bf31ac5 100644 --- a/dbScripts/ownradio_db_v3.sql +++ b/dbScripts/ownradio_db_v3.sql @@ -296,7 +296,7 @@ BEGIN RETURN QUERY SELECT CAST((nexttrack.track) AS CHARACTER VARYING), nexttrack.methodid - FROM getnexttrackid_v3(i_deviceid) AS nexttrack; + FROM getnexttrackid_v7(i_deviceid) AS nexttrack; END; $BODY$ LANGUAGE plpgsql VOLATILE @@ -362,7 +362,7 @@ ALTER FUNCTION public.getnexttrackid(uuid) OWNER TO postgres; - -- Function: public.getnexttrackid_string(uuid) +-- Function: public.getnexttrackid_string(uuid) -- DROP FUNCTION public.getnexttrackid_string(uuid); @@ -1055,3 +1055,215 @@ $BODY$ COST 100; ALTER FUNCTION public.calculateratios() OWNER TO postgres; + + + -- Function: getnexttrackid_v7(uuid) + +-- DROP FUNCTION getnexttrackid_v7(uuid); + +CREATE OR REPLACE FUNCTION getnexttrackid_v7(IN i_deviceid uuid) + RETURNS TABLE(track uuid, methodid integer) AS +$BODY$ + +-- Функция выдачи следующего трека пользователю +-- С учетом рекомендаций от других пользователей + +DECLARE + i_userid UUID = i_deviceid; + rnd INTEGER = (SELECT trunc(random() * 1001)); + o_methodid INTEGER; -- id метода выбора трека + owntracks INTEGER; -- количество "своих" треков пользователя (обрезаем на 900 шт) + arrusers uuid ARRAY; -- массив пользователей для i_userid с неотрицательнымм коэффициентами схожести интересов + exceptusers uuid ARRAY; -- массив пользователей для i_userid с котороми не было пересечений по трекам +BEGIN + -- Выбираем следующий трек + + -- Определяем количество "своих" треков пользователя, ограничивая его 900 + owntracks = (SELECT COUNT(*) + FROM ( + SELECT * + FROM ratings + WHERE userid = i_userid + AND ratingsum >= 0 + LIMIT 900) AS count); + + -- Если rnd меньше количества "своих" треков, выбираем трек из треков пользователя (добавленных им или прослушанных до конца) + -- с положительным рейтингом, за исключением прослушанных за последние сутки + + IF (rnd < owntracks) + THEN + o_methodid = 2; -- метод выбора из своих треков + RETURN QUERY + SELECT + trackid, + o_methodid + FROM ratings + WHERE userid = i_userid + AND lastlisten < localtimestamp - INTERVAL '1 day' + AND ratingsum >= 0 + AND (SELECT isexist + FROM tracks + WHERE recid = trackid) = 1 + AND ((SELECT length + FROM tracks + WHERE recid = trackid) >= 120 + OR (SELECT length + FROM tracks + WHERE recid = trackid) IS NULL) + AND ((SELECT iscensorial + FROM tracks + WHERE recid = trackid) IS NULL + OR (SELECT iscensorial + FROM tracks + WHERE recid = trackid) != 0) + AND trackid NOT IN (SELECT trackid + FROM downloadtracks + WHERE reccreated > localtimestamp - INTERVAL '1 day') + ORDER BY RANDOM() + LIMIT 1; + + -- Если такой трек найден - выход из функции, возврат найденного значения + IF FOUND + THEN RETURN; + END IF; + END IF; + + -- Если rnd больше количества "своих" треков - рекомендуем трек из треков пользователя с наибольшим + -- коэффициентом схожести интересов и наибольшим рейтингом прослушивания + + -- Выберем всех пользователей с неотрицательным коэффициентом схожести интересов для i_userid + -- отсортировав по убыванию коэффициентов + arrusers = (SELECT ARRAY (SELECT CASE WHEN userid1 = i_userid THEN userid2 + WHEN userid2 = i_userid THEN userid1 + ELSE NULL + END + FROM ratios + WHERE userid1 = i_userid OR userid2 = i_userid + AND ratio >= 0 + ORDER BY ratio DESC + )); + -- Выбираем пользователя i, с которым у него максимальный коэффициент. Среди его треков ищем трек + -- с максимальным рейтингом прослушивания, за исключением уже прослушанных пользователем i_userid. + -- Если рекомендовать нечего - берем следующего пользователя с наибольшим коэффициентом из оставшихся. + FOR i IN 1.. (SELECT COUNT (*) FROM unnest(arrusers)) LOOP + o_methodid = 4; -- метод выбора из рекомендованных треков + RETURN QUERY + SELECT + trackid, + o_methodid + FROM ratings + WHERE userid = arrusers[i] + AND ratingsum > 0 + AND trackid NOT IN (SELECT trackid FROM ratings WHERE userid = i_userid) + AND trackid NOT IN (SELECT trackid + FROM downloadtracks + WHERE deviceid = i_deviceid + AND reccreated > localtimestamp - INTERVAL '1 day') + AND (SELECT isexist + FROM tracks + WHERE recid = trackid) = 1 + AND ((SELECT length + FROM tracks + WHERE recid = trackid) >= 120 + OR (SELECT length + FROM tracks + WHERE recid = trackid) IS NULL) + AND ((SELECT iscensorial + FROM tracks + WHERE recid = trackid) IS NULL + OR (SELECT iscensorial + FROM tracks + WHERE recid = trackid) != 0) + ORDER BY ratingsum DESC + LIMIT 1; + -- Если нашли что рекомендовать - выходим из функции + IF found THEN + RETURN; + END IF; + END LOOP; + + -- При отсутствии рекомендаций, выдавать случайный трек из непрослушанных треков с неотрицательным + -- рейтингом среди пользователей с которыми не было пересечений по трекам. + exceptusers = (SELECT ARRAY ( + SELECT * FROM ( + SELECT recid FROM users WHERE recid != i_userid + EXCEPT + (SELECT CASE WHEN userid1 = i_userid THEN userid2 + WHEN userid2 = i_userid THEN userid1 + ELSE NULL + END + FROM ratios WHERE userid1 = i_userid OR userid2 = i_userid) + ) AS us + ORDER BY RANDOM() + ) + ); + FOR i IN 1.. (SELECT COUNT (*) FROM unnest(exceptusers)) LOOP + o_methodid = 6; -- метод выбора из непрослушанных треков с неотрицательным рейтингом среди пользователей с которыми не было пересечений + RETURN QUERY + SELECT + recid, + o_methodid + FROM tracks + WHERE recid IN (SELECT trackid FROM ratings WHERE userid = exceptusers[i] AND ratingsum >= 0) + AND recid NOT IN (SELECT trackid FROM ratings WHERE userid = i_userid) + AND isexist = 1 + AND (iscensorial IS NULL OR iscensorial != 0) + AND (length > 120 OR length IS NULL) + AND recid NOT IN (SELECT trackid + FROM downloadtracks + WHERE reccreated > localtimestamp - INTERVAL '1 day') + ORDER BY RANDOM() + LIMIT 1; + -- Если нашли что рекомендовать - выходим из функции + IF found THEN + RETURN; + ELSE + + END IF; + END LOOP; + + -- Если таких треков нет - выбираем случайный трек из ни разу не прослушанных пользователем треков + o_methodid = 3; -- метод выбора из непрослушанных треков + RETURN QUERY + SELECT + recid, + o_methodid + FROM tracks + WHERE recid NOT IN + (SELECT trackid + FROM ratings + WHERE userid = i_userid) + AND isexist = 1 + AND (iscensorial IS NULL OR iscensorial != 0) + AND (length > 120 OR length IS NULL) + AND recid NOT IN (SELECT trackid + FROM downloadtracks + WHERE reccreated > localtimestamp - INTERVAL '1 day') + ORDER BY RANDOM() + LIMIT 1; + + -- Если такой трек найден - выход из функции, возврат найденного значения + IF FOUND + THEN RETURN; + END IF; + + -- Если предыдущие запросы вернули null, выбираем случайный трек + o_methodid = 1; -- метод выбора случайного трека + RETURN QUERY + SELECT + recid, + o_methodid + FROM tracks + WHERE isexist = 1 + AND (iscensorial IS NULL OR iscensorial != 0) + AND (length > 120 OR length IS NULL) + ORDER BY RANDOM() + LIMIT 1; + RETURN; +END; +$BODY$ + LANGUAGE plpgsql VOLATILE + COST 100 + ROWS 1000; +ALTER FUNCTION getnexttrackid_v7(uuid) + OWNER TO "postgres"; diff --git a/v3/src/main/java/ownradio/domain/DownloadTrack.java b/v3/src/main/java/ownradio/domain/DownloadTrack.java index 447832d..dae5623 100644 --- a/v3/src/main/java/ownradio/domain/DownloadTrack.java +++ b/v3/src/main/java/ownradio/domain/DownloadTrack.java @@ -27,4 +27,6 @@ public class DownloadTrack extends AbstractEntity { @JoinColumn(name = "trackid") private Track track; + private Integer methodid; + } diff --git a/v3/src/main/java/ownradio/web/rest/v2/HistoryController.java b/v3/src/main/java/ownradio/web/rest/v2/HistoryController.java index b0a000f..14a56b9 100644 --- a/v3/src/main/java/ownradio/web/rest/v2/HistoryController.java +++ b/v3/src/main/java/ownradio/web/rest/v2/HistoryController.java @@ -47,7 +47,7 @@ public HistoryController(HistoryService historyService, TrackService trackServic public ResponseEntity save(@PathVariable UUID deviceId, @PathVariable UUID trackId, @RequestBody History history) { try { log.info("{} {}",deviceId.toString(),trackId.toString()); - log.info("{} {} {}",history.getLastListen(), history.getIsListen(), history.getMethod()); + log.info("{} {}",history.getLastListen(), history.getIsListen()); Track track = trackService.getById(trackId); Device device = deviceService.getById(deviceId); diff --git a/v3/src/main/java/ownradio/web/rest/v3/TrackController.java b/v3/src/main/java/ownradio/web/rest/v3/TrackController.java index 515dcc4..6c8546b 100644 --- a/v3/src/main/java/ownradio/web/rest/v3/TrackController.java +++ b/v3/src/main/java/ownradio/web/rest/v3/TrackController.java @@ -143,8 +143,9 @@ public ResponseEntity getNextTrack(@PathVariable UUID deviceId) { DownloadTrack downloadTrack = new DownloadTrack(); downloadTrack.setTrack(track); downloadTrack.setDevice(device); + downloadTrack.setMethodid(nextTrack.getMethodid()); downloadTrackRepository.saveAndFlush(downloadTrack); - log.info("getNextTrack return {}", trackInfo.get("id")); + log.info("getNextTrack return {} {}", nextTrack.getMethodid().toString(), trackInfo.get("id")); return new ResponseEntity<>(trackInfo, HttpStatus.OK); }catch (Exception ex){ log.info("{}", ex.getMessage()); diff --git a/v3/src/main/java/ownradio/web/v4/HistoryController.java b/v3/src/main/java/ownradio/web/v4/HistoryController.java new file mode 100644 index 0000000..6037aeb --- /dev/null +++ b/v3/src/main/java/ownradio/web/v4/HistoryController.java @@ -0,0 +1,99 @@ +package ownradio.web.v4; + +import lombok.Data; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import ownradio.domain.Device; +import ownradio.domain.History; +import ownradio.domain.Track; +import ownradio.service.DeviceService; +import ownradio.service.HistoryService; +import ownradio.service.TrackService; + +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.UUID; + +/** + * Created by a.polunina on 14.03.2017. + */ +@Slf4j +@RestController("HistoryControllerV4") +@RequestMapping("/v4/histories") +public class HistoryController { + private final HistoryService historyService; + private final TrackService trackService; + private final DeviceService deviceService; + + @Autowired + public HistoryController(HistoryService historyService, TrackService trackService, DeviceService deviceService) { + this.historyService = historyService; + this.trackService = trackService; + this.deviceService = deviceService; + } + + @Data + private static class HistoryDTO { + private UUID deviceId; + private UUID trackId; + private String lastListen; + private int isListen; // 1, -1 + private Integer methodid; + + public History getHistory() { + Calendar calendar; + try { + calendar = Calendar.getInstance(); + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'H:m:s"); + calendar.setTime( sdf.parse(lastListen)); + }catch (Exception ex){ + calendar = null; + } + + History history = new History(); + history.setLastListen(calendar); + history.setIsListen(isListen); + history.setMethodid(methodid); + + return history; + } + } + + //form-data + @RequestMapping(value = "/{deviceId}/{trackId}", method = RequestMethod.POST) + public ResponseEntity save(@PathVariable UUID deviceId, @PathVariable UUID trackId, HistoryDTO historyDTO) { + return getResponseEntity(deviceId, trackId, historyDTO.getHistory()); + } + + //json + @RequestMapping(value = "/{deviceId}/{trackId}", method = RequestMethod.POST, headers = "Content-Type=application/json") + public ResponseEntity save2(@PathVariable UUID deviceId, @PathVariable UUID trackId, @RequestBody History history) { + return getResponseEntity(deviceId, trackId, history); + } + + private ResponseEntity getResponseEntity(@PathVariable UUID deviceId, @PathVariable UUID trackId, @RequestBody History history) { + try { + log.info("deviceId:{} trackId: {}",deviceId.toString(),trackId.toString()); + log.info("{} {} {}",history.getLastListen(), history.getIsListen(), history.getMethodid()); + Track track = trackService.getById(trackId); + Device device = deviceService.getById(deviceId); + + if (track == null || device == null) { + throw new RuntimeException("Track or Device is null"); + } + + history.setTrack(track); + history.setDevice(device); + + historyService.save(history); + log.info("Save history, rating and update ratios"); + return new ResponseEntity(HttpStatus.OK); + } catch (Exception e) { + return new ResponseEntity(HttpStatus.INTERNAL_SERVER_ERROR); + } + } + +} \ No newline at end of file diff --git a/v3/src/main/java/ownradio/web/v4/TrackController.java b/v3/src/main/java/ownradio/web/v4/TrackController.java new file mode 100644 index 0000000..62db872 --- /dev/null +++ b/v3/src/main/java/ownradio/web/v4/TrackController.java @@ -0,0 +1,161 @@ +package ownradio.web.v4; + +import lombok.Data; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; +import ownradio.domain.Device; +import ownradio.domain.DownloadTrack; +import ownradio.domain.NextTrack; +import ownradio.domain.Track; +import ownradio.repository.DownloadTrackRepository; +import ownradio.repository.TrackRepository; +import ownradio.service.TrackService; +import ownradio.util.ResourceUtil; + +import java.io.File; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +/** + * Created by a.polunina on 14.03.2017. + */ +@Slf4j +@RestController("TrackControllerV4") +@RequestMapping(value = "/v4/tracks") +public class TrackController { + + private final TrackService trackService; + private final TrackRepository trackRepository; + private final DownloadTrackRepository downloadTrackRepository; + + @Autowired + public TrackController(TrackService trackService, TrackRepository trackRepository, DownloadTrackRepository downloadTrackRepository) { + this.trackService = trackService; + this.trackRepository = trackRepository; + this.downloadTrackRepository = downloadTrackRepository; + } + + @Data + private static class TrackDTO { + private UUID fileGuid; + private String fileName; + private String filePath; + private UUID deviceId; + private MultipartFile musicFile; + + public Track getTrack() { + Device device = new Device(); + device.setRecid(deviceId); + + Track track = new Track(); + track.setRecid(fileGuid); + track.setDevice(device); + track.setPath("---"); + track.setLocaldevicepathupload(filePath); + + return track; + } + } + + @RequestMapping(method = RequestMethod.POST) + public ResponseEntity save(TrackDTO trackDTO) { + if (trackDTO.getMusicFile().isEmpty()) { + return new ResponseEntity(HttpStatus.BAD_REQUEST); + } + + try { + trackService.save(trackDTO.getTrack(), trackDTO.getMusicFile()); + trackService.setTrackInfo(trackDTO.getTrack().getRecid()); + return new ResponseEntity(HttpStatus.CREATED); + } catch (Exception e) { + return new ResponseEntity(HttpStatus.INTERNAL_SERVER_ERROR); + } + + } + + @RequestMapping(value = "/{id}", method = RequestMethod.GET) + public ResponseEntity getTrack(@PathVariable UUID id) { + Track track = trackService.getById(id); + + if (track != null) { + byte[] bytes = ResourceUtil.read(track.getPath()); + return new ResponseEntity<>(bytes, getHttpAudioHeaders(), HttpStatus.OK); + } else { + return new ResponseEntity<>(HttpStatus.NOT_FOUND); + } + } + + private HttpHeaders getHttpAudioHeaders() { + HttpHeaders responseHeaders = new HttpHeaders(); + responseHeaders.add("Content-Type", "audio/mpeg"); + return responseHeaders; + } + + @RequestMapping(value = "/{deviceId}/next", method = RequestMethod.GET) + public ResponseEntity getNextTrack(@PathVariable UUID deviceId) { + NextTrack nextTrack = null; + try { + nextTrack = trackService.getNextTrackIdV2(deviceId); + }catch (Exception ex){ + + } + + Map trackInfo = new HashMap<>(); + if (nextTrack != null) { + try { + Track track = trackRepository.findOne(nextTrack.getTrackid()); + + File file = new File(track.getPath()); + if(!file.exists()){ + track.setIsexist(0); + trackRepository.saveAndFlush(track); + return getNextTrack(deviceId); + } + + if(track.getIsfilledinfo() == null || track.getIsfilledinfo() != 1) + trackService.setTrackInfo(track.getRecid()); + + if(track.getIscensorial() != null && track.getIscensorial() == 0) + return getNextTrack(deviceId); + if(track.getLength() < 120) + return getNextTrack(deviceId); + + trackInfo.put("id", nextTrack.getTrackid().toString()); + trackInfo.put("length", String.valueOf(track.getLength())); + if(track.getRecname() != null && !track.getRecname().isEmpty() && !track.getRecname().equals("null")) + trackInfo.put("name", track.getRecname()); + else + trackInfo.put("name", "Unknown track"); + if(track.getArtist() != null && !track.getArtist().isEmpty() && !track.getArtist().equals("null")) + trackInfo.put("artist", track.getArtist()); + else + trackInfo.put("artist", "Unknown artist"); + + //Сохраняем информацию об отданном треке + Device device = new Device(); + device.setRecid(deviceId); + DownloadTrack downloadTrack = new DownloadTrack(); + downloadTrack.setTrack(track); + downloadTrack.setDevice(device); + downloadTrack.setMethodid(nextTrack.getMethodid()); + downloadTrackRepository.saveAndFlush(downloadTrack); + log.info("getNextTrack return {} {}", nextTrack.getMethodid().toString(), trackInfo.get("id")); + return new ResponseEntity<>(trackInfo, HttpStatus.OK); + }catch (Exception ex){ + log.info("{}", ex.getMessage()); + return new ResponseEntity<>(HttpStatus.NOT_FOUND); + } + } else { + return new ResponseEntity<>(HttpStatus.NOT_FOUND); + } + } +} diff --git a/v3/src/main/resources/data/postgresql/schema.sql b/v3/src/main/resources/data/postgresql/schema.sql index 435d573..1cfb30b 100644 --- a/v3/src/main/resources/data/postgresql/schema.sql +++ b/v3/src/main/resources/data/postgresql/schema.sql @@ -539,7 +539,7 @@ END; LANGUAGE plpgsql; -CREATE OR REPLACE FUNCTION public.getnexttrackid_v6(IN i_deviceid uuid) +CREATE OR REPLACE FUNCTION getnexttrackid_v6(IN i_deviceid uuid) RETURNS TABLE(track uuid, methodid integer) AS ' DECLARE @@ -720,4 +720,205 @@ BEGIN RETURN; END; ' -LANGUAGE plpgsql; \ No newline at end of file +LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION getnexttrackid_v7(IN i_deviceid uuid) + RETURNS TABLE(track uuid, methodid integer) AS +' +-- Функция выдачи следующего трека пользователю +-- С учетом рекомендаций от других пользователей + +DECLARE + i_userid UUID = i_deviceid; + rnd INTEGER = (SELECT trunc(random() * 1001)); + o_methodid INTEGER; -- id метода выбора трека + owntracks INTEGER; -- количество "своих" треков пользователя (обрезаем на 900 шт) + arrusers uuid ARRAY; -- массив пользователей для i_userid с неотрицательнымм коэффициентами схожести интересов + exceptusers uuid ARRAY; -- массив пользователей для i_userid с котороми не было пересечений по трекам +BEGIN + -- Выбираем следующий трек + + -- Определяем количество "своих" треков пользователя, ограничивая его 900 + owntracks = (SELECT COUNT(*) + FROM ( + SELECT * + FROM ratings + WHERE userid = i_userid + AND ratingsum >= 0 + LIMIT 900) AS count); + + -- Если rnd меньше количества "своих" треков, выбираем трек из треков пользователя (добавленных им или прослушанных до конца) + -- с положительным рейтингом, за исключением прослушанных за последние сутки + + IF (rnd < owntracks) + THEN + o_methodid = 2; -- метод выбора из своих треков + RETURN QUERY + SELECT + trackid, + o_methodid + FROM ratings + WHERE userid = i_userid + AND lastlisten < localtimestamp - INTERVAL ''1 day'' + AND ratingsum >= 0 + AND (SELECT isexist + FROM tracks + WHERE recid = trackid) = 1 + AND ((SELECT length + FROM tracks + WHERE recid = trackid) >= 120 + OR (SELECT length + FROM tracks + WHERE recid = trackid) IS NULL) + AND ((SELECT iscensorial + FROM tracks + WHERE recid = trackid) IS NULL + OR (SELECT iscensorial + FROM tracks + WHERE recid = trackid) != 0) + AND trackid NOT IN (SELECT trackid + FROM downloadtracks + WHERE reccreated > localtimestamp - INTERVAL ''1 day'') + ORDER BY RANDOM() + LIMIT 1; + + -- Если такой трек найден - выход из функции, возврат найденного значения + IF FOUND + THEN RETURN; + END IF; + END IF; + + -- Если rnd больше количества "своих" треков - рекомендуем трек из треков пользователя с наибольшим + -- коэффициентом схожести интересов и наибольшим рейтингом прослушивания + + -- Выберем всех пользователей с неотрицательным коэффициентом схожести интересов для i_userid + -- отсортировав по убыванию коэффициентов + arrusers = (SELECT ARRAY (SELECT CASE WHEN userid1 = i_userid THEN userid2 + WHEN userid2 = i_userid THEN userid1 + ELSE NULL + END + FROM ratios + WHERE userid1 = i_userid OR userid2 = i_userid + AND ratio >= 0 + ORDER BY ratio DESC + )); + -- Выбираем пользователя i, с которым у него максимальный коэффициент. Среди его треков ищем трек + -- с максимальным рейтингом прослушивания, за исключением уже прослушанных пользователем i_userid. + -- Если рекомендовать нечего - берем следующего пользователя с наибольшим коэффициентом из оставшихся. + FOR i IN 1.. (SELECT COUNT (*) FROM unnest(arrusers)) LOOP + o_methodid = 4; -- метод выбора из рекомендованных треков + RETURN QUERY + SELECT + trackid, + o_methodid + FROM ratings + WHERE userid = arrusers[i] + AND ratingsum > 0 + AND trackid NOT IN (SELECT trackid FROM ratings WHERE userid = i_userid) + AND trackid NOT IN (SELECT trackid + FROM downloadtracks + WHERE deviceid = i_deviceid + AND reccreated > localtimestamp - INTERVAL ''1 day'') + AND (SELECT isexist + FROM tracks + WHERE recid = trackid) = 1 + AND ((SELECT length + FROM tracks + WHERE recid = trackid) >= 120 + OR (SELECT length + FROM tracks + WHERE recid = trackid) IS NULL) + AND ((SELECT iscensorial + FROM tracks + WHERE recid = trackid) IS NULL + OR (SELECT iscensorial + FROM tracks + WHERE recid = trackid) != 0) + ORDER BY ratingsum DESC + LIMIT 1; + -- Если нашли что рекомендовать - выходим из функции + IF found THEN + RETURN; + END IF; + END LOOP; + -- При отсутствии рекомендаций, выдавать случайный трек из непрослушанных треков с неотрицательным + -- рейтингом среди пользователей с которыми не было пересечений по трекам. + exceptusers = (SELECT ARRAY ( + SELECT * FROM ( + SELECT recid FROM users WHERE recid != i_userid + EXCEPT + (SELECT CASE WHEN userid1 = i_userid THEN userid2 + WHEN userid2 = i_userid THEN userid1 + ELSE NULL + END + FROM ratios WHERE userid1 = i_userid OR userid2 = i_userid) + ) AS us + ORDER BY RANDOM() + ) + ); + FOR i IN 1.. (SELECT COUNT (*) FROM unnest(exceptusers)) LOOP + o_methodid = 6; -- метод выбора из непрослушанных треков с неотрицательным рейтингом среди пользователей с которыми не было пересечений + RETURN QUERY + SELECT + recid, + o_methodid + FROM tracks + WHERE recid IN (SELECT trackid FROM ratings WHERE userid = exceptusers[i] AND ratingsum >= 0) + AND recid NOT IN (SELECT trackid FROM ratings WHERE userid = i_userid) + AND isexist = 1 + AND (iscensorial IS NULL OR iscensorial != 0) + AND (length > 120 OR length IS NULL) + AND recid NOT IN (SELECT trackid + FROM downloadtracks + WHERE reccreated > localtimestamp - INTERVAL ''1 day'') + ORDER BY RANDOM() + LIMIT 1; + -- Если нашли что рекомендовать - выходим из функции + IF found THEN + RETURN; + ELSE + + END IF; + END LOOP; + + -- Если таких треков нет - выбираем случайный трек из ни разу не прослушанных пользователем треков + o_methodid = 3; -- метод выбора из непрослушанных треков + RETURN QUERY + SELECT + recid, + o_methodid + FROM tracks + WHERE recid NOT IN + (SELECT trackid + FROM ratings + WHERE userid = i_userid) + AND isexist = 1 + AND (iscensorial IS NULL OR iscensorial != 0) + AND (length > 120 OR length IS NULL) + AND recid NOT IN (SELECT trackid + FROM downloadtracks + WHERE reccreated > localtimestamp - INTERVAL ''1 day'') + ORDER BY RANDOM() + LIMIT 1; + + -- Если такой трек найден - выход из функции, возврат найденного значения + IF FOUND + THEN RETURN; + END IF; + + -- Если предыдущие запросы вернули null, выбираем случайный трек + o_methodid = 1; -- метод выбора случайного трека + RETURN QUERY + SELECT + recid, + o_methodid + FROM tracks + WHERE isexist = 1 + AND (iscensorial IS NULL OR iscensorial != 0) + AND (length > 120 OR length IS NULL) + ORDER BY RANDOM() + LIMIT 1; + RETURN; +END; +' +LANGUAGE plpgsql; From 9f3bed75b8219d2c3fd701b8c1bb6cc55496a3d5 Mon Sep 17 00:00:00 2001 From: Alexandra Polunina Date: Tue, 14 Mar 2017 17:07:38 +0300 Subject: [PATCH 28/44] =?UTF-8?q?=D0=9F=D0=BE=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=B8=D0=BB=D0=B0=20readme?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index ba6e7d3..2d11628 100644 --- a/README.md +++ b/README.md @@ -83,7 +83,8 @@ Content-Type →application/json;charset=UTF-8 "length": "duration", "name": "Title", "id": "00000000-0000-0000-0000-000000000000" - +} + ### Сохранение истории треков ##### POST /v4/histories/{deviceId}/{trackId} * `{trackId}` – UUID прослушанного трека @@ -94,5 +95,3 @@ Content-Type →application/json;charset=UTF-8 ##### HttpStatus * `200, "OK"` – если все ок * `500, "Internal Server Error"` – если произошел сбой на сервере - -} \ No newline at end of file From eb48f0ed77bd80db9a37ea0412ae04cfab14402a Mon Sep 17 00:00:00 2001 From: Alexandra Polunina Date: Fri, 24 Mar 2017 17:13:15 +0300 Subject: [PATCH 29/44] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D1=8B=20=D0=B8=20=D0=B8=D0=B7=D0=BC=D0=B5=D0=BD?= =?UTF-8?q?=D0=B5=D0=BD=D1=8B=20=D1=84=D1=83=D0=BD=D0=BA=D1=86=D0=B8=D0=B8?= =?UTF-8?q?=20=D0=B4=D0=BB=D1=8F=20=D1=81=D0=B1=D0=BE=D1=80=D0=B0=20=D0=B8?= =?UTF-8?q?=20=D0=BF=D1=80=D0=BE=D1=81=D0=BC=D0=BE=D1=82=D1=80=D0=B0=20?= =?UTF-8?q?=D1=81=D1=82=D0=B0=D1=82=D0=B8=D1=81=D1=82=D0=B8=D0=BA=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/ownradio/domain/DownloadTrack.java | 5 + .../main/java/ownradio/domain/NextTrack.java | 2 + .../java/ownradio/domain/UsersRating.java | 33 ++ .../ownradio/repository/DeviceRepository.java | 5 + .../repository/DownloadTrackRepository.java | 3 + .../ownradio/repository/UserRepository.java | 5 + .../java/ownradio/service/DeviceService.java | 4 + .../service/DownloadTrackService.java | 5 + .../java/ownradio/service/TrackService.java | 2 - .../java/ownradio/service/UserService.java | 5 + .../service/impl/DeviceServiceImpl.java | 6 + .../impl/DownloadTrackServiceImpl.java | 11 +- .../service/impl/HistoryServiceImpl.java | 8 +- .../service/impl/TrackServiceImpl.java | 2 + .../service/impl/UserServiceImpl.java | 27 ++ .../web/rest/v3/HistoryController.java | 5 + .../web/{ => rest}/v4/HistoryController.java | 6 +- .../web/rest/v4/StatisticsController.java | 77 +++++ .../web/{ => rest}/v4/TrackController.java | 15 +- .../main/resources/data/postgresql/schema.sql | 281 +++++++++++++++++- 20 files changed, 484 insertions(+), 23 deletions(-) create mode 100644 v3/src/main/java/ownradio/domain/UsersRating.java rename v3/src/main/java/ownradio/web/{ => rest}/v4/HistoryController.java (94%) create mode 100644 v3/src/main/java/ownradio/web/rest/v4/StatisticsController.java rename v3/src/main/java/ownradio/web/{ => rest}/v4/TrackController.java (93%) diff --git a/v3/src/main/java/ownradio/domain/DownloadTrack.java b/v3/src/main/java/ownradio/domain/DownloadTrack.java index dae5623..c214eb6 100644 --- a/v3/src/main/java/ownradio/domain/DownloadTrack.java +++ b/v3/src/main/java/ownradio/domain/DownloadTrack.java @@ -6,6 +6,7 @@ import lombok.Setter; import javax.persistence.*; +import java.util.UUID; /** * Сущность для хранения информации о скаченных треках @@ -29,4 +30,8 @@ public class DownloadTrack extends AbstractEntity { private Integer methodid; + private UUID userrecommend; + + private String txtrecommendinfo; + } diff --git a/v3/src/main/java/ownradio/domain/NextTrack.java b/v3/src/main/java/ownradio/domain/NextTrack.java index a9f49cc..74a5d01 100644 --- a/v3/src/main/java/ownradio/domain/NextTrack.java +++ b/v3/src/main/java/ownradio/domain/NextTrack.java @@ -13,4 +13,6 @@ public class NextTrack { private UUID trackid; private Integer methodid; + private UUID useridrecommended; + private String txtrecommendedinfo; } diff --git a/v3/src/main/java/ownradio/domain/UsersRating.java b/v3/src/main/java/ownradio/domain/UsersRating.java new file mode 100644 index 0000000..6cedcae --- /dev/null +++ b/v3/src/main/java/ownradio/domain/UsersRating.java @@ -0,0 +1,33 @@ +package ownradio.domain; + +import lombok.Getter; +import lombok.Setter; +import org.springframework.format.annotation.DateTimeFormat; + +import javax.persistence.TemporalType; +import java.math.BigInteger; +import java.util.Calendar; +import java.util.UUID; + +/** + * Created by a.polunina on 22.03.2017. + */ +@Setter +@Getter +public class UsersRating{ + private UUID userid; + + @DateTimeFormat(pattern = "dd-MM-yyyy'T'H:m:s") + //@Temporal(TemporalType.TIMESTAMP) + private String reccreated; + + private String recname; + + @DateTimeFormat(pattern = "dd-MM-yyyy'T'H:m:s") +// @Temporal(TemporalType.TIMESTAMP) + private String recupdated; + + private BigInteger owntracks; + + private BigInteger lasttracks; +} diff --git a/v3/src/main/java/ownradio/repository/DeviceRepository.java b/v3/src/main/java/ownradio/repository/DeviceRepository.java index a7c0877..d25a186 100644 --- a/v3/src/main/java/ownradio/repository/DeviceRepository.java +++ b/v3/src/main/java/ownradio/repository/DeviceRepository.java @@ -1,8 +1,10 @@ package ownradio.repository; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; import ownradio.domain.Device; +import java.util.List; import java.util.UUID; /** @@ -11,4 +13,7 @@ * @author Alpenov Tanat */ public interface DeviceRepository extends JpaRepository { + @Query(value = "select * from devices where userid = ?1", nativeQuery = true) + List getUserDevices(UUID userid); + } diff --git a/v3/src/main/java/ownradio/repository/DownloadTrackRepository.java b/v3/src/main/java/ownradio/repository/DownloadTrackRepository.java index 269e625..17e4c0d 100644 --- a/v3/src/main/java/ownradio/repository/DownloadTrackRepository.java +++ b/v3/src/main/java/ownradio/repository/DownloadTrackRepository.java @@ -6,6 +6,7 @@ import ownradio.domain.DownloadTrack; import ownradio.domain.Track; +import java.util.List; import java.util.UUID; /** @@ -17,4 +18,6 @@ public interface DownloadTrackRepository extends JpaRepository getLastTracksByDevice(UUID deviceid, Integer countTracks); } diff --git a/v3/src/main/java/ownradio/repository/UserRepository.java b/v3/src/main/java/ownradio/repository/UserRepository.java index 952596e..0e7beb8 100644 --- a/v3/src/main/java/ownradio/repository/UserRepository.java +++ b/v3/src/main/java/ownradio/repository/UserRepository.java @@ -1,8 +1,10 @@ package ownradio.repository; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; import ownradio.domain.User; +import java.util.List; import java.util.UUID; /** @@ -11,4 +13,7 @@ * @author Alpenov Tanat */ public interface UserRepository extends JpaRepository { + + @Query(value = "select * from getusersrating(?1)", nativeQuery = true) + List getUsersRating(Integer countRows); } diff --git a/v3/src/main/java/ownradio/service/DeviceService.java b/v3/src/main/java/ownradio/service/DeviceService.java index 10974e6..f4787b4 100644 --- a/v3/src/main/java/ownradio/service/DeviceService.java +++ b/v3/src/main/java/ownradio/service/DeviceService.java @@ -1,7 +1,9 @@ package ownradio.service; +import org.springframework.data.jpa.repository.Query; import ownradio.domain.Device; +import java.util.List; import java.util.UUID; /** @@ -13,4 +15,6 @@ public interface DeviceService { void save(Device device); Device getById(UUID uuid); + + List getByUserid(UUID userid); } diff --git a/v3/src/main/java/ownradio/service/DownloadTrackService.java b/v3/src/main/java/ownradio/service/DownloadTrackService.java index 7c0b3f4..ea316b3 100644 --- a/v3/src/main/java/ownradio/service/DownloadTrackService.java +++ b/v3/src/main/java/ownradio/service/DownloadTrackService.java @@ -2,6 +2,9 @@ import ownradio.domain.DownloadTrack; +import java.util.List; +import java.util.UUID; + /** * Интерфейс сервиса, для работы с историей скаченных треков * @@ -9,4 +12,6 @@ */ public interface DownloadTrackService { void save(DownloadTrack downloadTrack); + + List getLastTracksByDevice(UUID deviceId, Integer countTracks); } diff --git a/v3/src/main/java/ownradio/service/TrackService.java b/v3/src/main/java/ownradio/service/TrackService.java index 8bad137..4a10806 100644 --- a/v3/src/main/java/ownradio/service/TrackService.java +++ b/v3/src/main/java/ownradio/service/TrackService.java @@ -4,8 +4,6 @@ import ownradio.domain.NextTrack; import ownradio.domain.Track; -import java.util.Collection; -import java.util.List; import java.util.UUID; /** diff --git a/v3/src/main/java/ownradio/service/UserService.java b/v3/src/main/java/ownradio/service/UserService.java index 54037f3..2a78481 100644 --- a/v3/src/main/java/ownradio/service/UserService.java +++ b/v3/src/main/java/ownradio/service/UserService.java @@ -1,7 +1,9 @@ package ownradio.service; import ownradio.domain.User; +import ownradio.domain.UsersRating; +import java.util.List; import java.util.UUID; /** @@ -13,4 +15,7 @@ public interface UserService { User getById(UUID id); User save(User user); + + List getUsersRating(Integer countRows); + } diff --git a/v3/src/main/java/ownradio/service/impl/DeviceServiceImpl.java b/v3/src/main/java/ownradio/service/impl/DeviceServiceImpl.java index d6b6a0a..a70492a 100644 --- a/v3/src/main/java/ownradio/service/impl/DeviceServiceImpl.java +++ b/v3/src/main/java/ownradio/service/impl/DeviceServiceImpl.java @@ -6,6 +6,7 @@ import ownradio.repository.DeviceRepository; import ownradio.service.DeviceService; +import java.util.List; import java.util.UUID; @Service @@ -28,4 +29,9 @@ public void save(Device device) { public Device getById(UUID uuid) { return deviceRepository.findOne(uuid); } + + @Override + public List getByUserid(UUID uuid) { + return deviceRepository.getUserDevices(uuid); + } } diff --git a/v3/src/main/java/ownradio/service/impl/DownloadTrackServiceImpl.java b/v3/src/main/java/ownradio/service/impl/DownloadTrackServiceImpl.java index 0257de2..37f4714 100644 --- a/v3/src/main/java/ownradio/service/impl/DownloadTrackServiceImpl.java +++ b/v3/src/main/java/ownradio/service/impl/DownloadTrackServiceImpl.java @@ -2,10 +2,14 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import ownradio.domain.DownloadTrack; import ownradio.repository.DownloadTrackRepository; import ownradio.service.DownloadTrackService; +import java.util.List; +import java.util.UUID; + @Service public class DownloadTrackServiceImpl implements DownloadTrackService { @@ -16,9 +20,14 @@ public DownloadTrackServiceImpl(DownloadTrackRepository downloadTrackRepository) this.downloadTrackRepository = downloadTrackRepository; } - @Override public void save(DownloadTrack downloadTrack) { downloadTrackRepository.saveAndFlush(downloadTrack); } + + @Override + @Transactional + public List getLastTracksByDevice(UUID deviceId, Integer countTracks) { + return downloadTrackRepository.getLastTracksByDevice(deviceId, countTracks); + } } diff --git a/v3/src/main/java/ownradio/service/impl/HistoryServiceImpl.java b/v3/src/main/java/ownradio/service/impl/HistoryServiceImpl.java index bceef04..bde07df 100644 --- a/v3/src/main/java/ownradio/service/impl/HistoryServiceImpl.java +++ b/v3/src/main/java/ownradio/service/impl/HistoryServiceImpl.java @@ -1,14 +1,10 @@ package ownradio.service.impl; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.jdbc.core.SqlParameter; -import org.springframework.jdbc.core.simple.SimpleJdbcCall; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import ownradio.domain.History; import ownradio.domain.Rating; -import ownradio.repository.DownloadTrackRepository; import ownradio.repository.HistoryRepository; import ownradio.repository.RatingRepository; import ownradio.repository.RatioRepository; @@ -20,14 +16,12 @@ public class HistoryServiceImpl implements HistoryService { private final HistoryRepository historyRepository; private final RatingRepository ratingRepository; private final RatioRepository ratioRepository; - private final DownloadTrackRepository downloadTrackRepository; @Autowired - public HistoryServiceImpl(HistoryRepository historyRepository, RatingRepository ratingRepository, RatioRepository ratioRepository, DownloadTrackRepository downloadTrackRepository) { + public HistoryServiceImpl(HistoryRepository historyRepository, RatingRepository ratingRepository, RatioRepository ratioRepository) { this.historyRepository = historyRepository; this.ratingRepository = ratingRepository; this.ratioRepository = ratioRepository; - this.downloadTrackRepository = downloadTrackRepository; } @Transactional diff --git a/v3/src/main/java/ownradio/service/impl/TrackServiceImpl.java b/v3/src/main/java/ownradio/service/impl/TrackServiceImpl.java index 011068d..25d35c8 100644 --- a/v3/src/main/java/ownradio/service/impl/TrackServiceImpl.java +++ b/v3/src/main/java/ownradio/service/impl/TrackServiceImpl.java @@ -48,6 +48,8 @@ public NextTrack getNextTrackIdV2(UUID deviceId) { if(objects != null) { nextTrack.setTrackid(UUID.fromString((String) objects.get(0)[0])); nextTrack.setMethodid((Integer) objects.get(0)[1]); + if(objects.get(0)[2] != null) nextTrack.setUseridrecommended(UUID.fromString((String) objects.get(0)[2])); + if(objects.get(0)[3] != null) nextTrack.setTxtrecommendedinfo((String) objects.get(0)[3]); return nextTrack; }else{ return null; diff --git a/v3/src/main/java/ownradio/service/impl/UserServiceImpl.java b/v3/src/main/java/ownradio/service/impl/UserServiceImpl.java index c5c88dc..c9be972 100644 --- a/v3/src/main/java/ownradio/service/impl/UserServiceImpl.java +++ b/v3/src/main/java/ownradio/service/impl/UserServiceImpl.java @@ -4,9 +4,13 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import ownradio.domain.User; +import ownradio.domain.UsersRating; import ownradio.repository.UserRepository; import ownradio.service.UserService; +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.List; import java.util.UUID; @Service @@ -23,4 +27,27 @@ public User getById(UUID id) { public User save(User user) { return userRepository.saveAndFlush(user); } + + @Override + @Transactional + public List getUsersRating(Integer countRows) { + List usersRating = new ArrayList(); + List objects = userRepository.getUsersRating(countRows); + if (objects != null) { + for (int i = 0; i < objects.size(); i++) { + UsersRating userRating = new UsersRating(); + userRating.setUserid(UUID.fromString((String) objects.get(i)[0])); + userRating.setReccreated((String) objects.get(i)[1]); + userRating.setRecname((String) objects.get(i)[2]); + userRating.setRecupdated((String) objects.get(i)[3]); + userRating.setOwntracks((BigInteger) objects.get(i)[4]); + userRating.setLasttracks((BigInteger) objects.get(i)[5]); + + usersRating.add(userRating); + } + } else { + return null; + } + return usersRating; + } } diff --git a/v3/src/main/java/ownradio/web/rest/v3/HistoryController.java b/v3/src/main/java/ownradio/web/rest/v3/HistoryController.java index bf7970f..902a1dd 100644 --- a/v3/src/main/java/ownradio/web/rest/v3/HistoryController.java +++ b/v3/src/main/java/ownradio/web/rest/v3/HistoryController.java @@ -9,6 +9,8 @@ import ownradio.domain.Device; import ownradio.domain.History; import ownradio.domain.Track; +import ownradio.repository.DeviceRepository; +import ownradio.repository.TrackRepository; import ownradio.service.DeviceService; import ownradio.service.HistoryService; import ownradio.service.TrackService; @@ -76,6 +78,9 @@ public ResponseEntity save2(@PathVariable UUID deviceId, @PathVariable UUID trac private ResponseEntity getResponseEntity(@PathVariable UUID deviceId, @PathVariable UUID trackId, @RequestBody History history) { try { + if(deviceService.getById(deviceId) == null || trackService.getById(trackId) == null) + return new ResponseEntity(HttpStatus.OK); + log.info("deviceId:{} trackId: {}",deviceId.toString(),trackId.toString()); log.info("{} {} {}",history.getLastListen(), history.getIsListen(), history.getMethodid()); Track track = trackService.getById(trackId); diff --git a/v3/src/main/java/ownradio/web/v4/HistoryController.java b/v3/src/main/java/ownradio/web/rest/v4/HistoryController.java similarity index 94% rename from v3/src/main/java/ownradio/web/v4/HistoryController.java rename to v3/src/main/java/ownradio/web/rest/v4/HistoryController.java index 6037aeb..e592d53 100644 --- a/v3/src/main/java/ownradio/web/v4/HistoryController.java +++ b/v3/src/main/java/ownradio/web/rest/v4/HistoryController.java @@ -1,4 +1,4 @@ -package ownradio.web.v4; +package ownradio.web.rest.v4; import lombok.Data; import lombok.extern.slf4j.Slf4j; @@ -21,6 +21,7 @@ * Created by a.polunina on 14.03.2017. */ @Slf4j +//@CrossOrigin @RestController("HistoryControllerV4") @RequestMapping("/v4/histories") public class HistoryController { @@ -76,6 +77,9 @@ public ResponseEntity save2(@PathVariable UUID deviceId, @PathVariable UUID trac private ResponseEntity getResponseEntity(@PathVariable UUID deviceId, @PathVariable UUID trackId, @RequestBody History history) { try { + if(deviceService.getById(deviceId) == null || trackService.getById(trackId) == null) + return new ResponseEntity(HttpStatus.BAD_REQUEST); + log.info("deviceId:{} trackId: {}",deviceId.toString(),trackId.toString()); log.info("{} {} {}",history.getLastListen(), history.getIsListen(), history.getMethodid()); Track track = trackService.getById(trackId); diff --git a/v3/src/main/java/ownradio/web/rest/v4/StatisticsController.java b/v3/src/main/java/ownradio/web/rest/v4/StatisticsController.java new file mode 100644 index 0000000..ae95b5c --- /dev/null +++ b/v3/src/main/java/ownradio/web/rest/v4/StatisticsController.java @@ -0,0 +1,77 @@ +package ownradio.web.rest.v4; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import ownradio.domain.Device; +import ownradio.domain.DownloadTrack; +import ownradio.domain.UsersRating; +import ownradio.service.DeviceService; +import ownradio.service.DownloadTrackService; +import ownradio.service.UserService; + +import java.util.List; +import java.util.UUID; + +/** + * Created by a.polunina on 17.03.2017. + */ +@Slf4j +//@CrossOrigin +@RestController +@RequestMapping("/v4/statistics") +public class StatisticsController { + + private final UserService userService; + private final DeviceService deviceService; + private final DownloadTrackService downloadTrackService; + + @Autowired + public StatisticsController(UserService userService, DeviceService deviceService, DownloadTrackService downloadTrackService){ + this.userService = userService; + this.deviceService = deviceService; + this.downloadTrackService = downloadTrackService; + + } + + @RequestMapping(value = "/{userId}/getuserdevices", method = RequestMethod.GET) + public ResponseEntity getUserDevices(@PathVariable UUID userId) { + try { + List devices = deviceService.getByUserid(userId); + + return new ResponseEntity<>(devices, HttpStatus.OK); + }catch (Exception ex){ + return new ResponseEntity<>(HttpStatus.NOT_FOUND); + } + } + + + @RequestMapping(value = "/{deviceId}/{countTracks}/getlasttracks", method = RequestMethod.GET) + public ResponseEntity getLastTracksByDevice(@PathVariable UUID deviceId, @PathVariable Integer countTracks) { + try { + List downloadedTracks = downloadTrackService.getLastTracksByDevice(deviceId, countTracks); +// Device device = new Device(); +// device.setRecid(deviceId); +// List downloadedTracks = downloadTrackRepository.findFirst3ByDeviceOrderByReccreatedDesc(device); + + return new ResponseEntity<>(downloadedTracks, HttpStatus.OK); + }catch (Exception ex){ + return new ResponseEntity<>(HttpStatus.NOT_FOUND); + } + } + + + @RequestMapping(value = "/usersrating/{countRows}", method = RequestMethod.GET) + public ResponseEntity getUsersRating(@PathVariable Integer countRows) { + try { + List usersRating = userService.getUsersRating(countRows); + + return new ResponseEntity<>(usersRating, HttpStatus.OK); + }catch (Exception ex){ + return new ResponseEntity<>(HttpStatus.NOT_FOUND); + } + } +} + diff --git a/v3/src/main/java/ownradio/web/v4/TrackController.java b/v3/src/main/java/ownradio/web/rest/v4/TrackController.java similarity index 93% rename from v3/src/main/java/ownradio/web/v4/TrackController.java rename to v3/src/main/java/ownradio/web/rest/v4/TrackController.java index 62db872..3036e8e 100644 --- a/v3/src/main/java/ownradio/web/v4/TrackController.java +++ b/v3/src/main/java/ownradio/web/rest/v4/TrackController.java @@ -1,4 +1,4 @@ -package ownradio.web.v4; +package ownradio.web.rest.v4; import lombok.Data; import lombok.extern.slf4j.Slf4j; @@ -6,10 +6,7 @@ import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; import ownradio.domain.Device; import ownradio.domain.DownloadTrack; @@ -29,6 +26,7 @@ * Created by a.polunina on 14.03.2017. */ @Slf4j +//@CrossOrigin @RestController("TrackControllerV4") @RequestMapping(value = "/v4/tracks") public class TrackController { @@ -126,7 +124,7 @@ public ResponseEntity getNextTrack(@PathVariable UUID deviceId) { if(track.getIscensorial() != null && track.getIscensorial() == 0) return getNextTrack(deviceId); - if(track.getLength() < 120) + if(track.getLength() != null && track.getLength() < 120) return getNextTrack(deviceId); trackInfo.put("id", nextTrack.getTrackid().toString()); @@ -143,12 +141,15 @@ public ResponseEntity getNextTrack(@PathVariable UUID deviceId) { //Сохраняем информацию об отданном треке Device device = new Device(); device.setRecid(deviceId); + DownloadTrack downloadTrack = new DownloadTrack(); downloadTrack.setTrack(track); downloadTrack.setDevice(device); downloadTrack.setMethodid(nextTrack.getMethodid()); + downloadTrack.setUserrecommend(nextTrack.getUseridrecommended()); + downloadTrack.setTxtrecommendinfo(nextTrack.getTxtrecommendedinfo()); downloadTrackRepository.saveAndFlush(downloadTrack); - log.info("getNextTrack return {} {}", nextTrack.getMethodid().toString(), trackInfo.get("id")); + log.info("getNextTrack return {} {}", nextTrack.getMethodid(), trackInfo.get("id")); return new ResponseEntity<>(trackInfo, HttpStatus.OK); }catch (Exception ex){ log.info("{}", ex.getMessage()); diff --git a/v3/src/main/resources/data/postgresql/schema.sql b/v3/src/main/resources/data/postgresql/schema.sql index 1cfb30b..9032def 100644 --- a/v3/src/main/resources/data/postgresql/schema.sql +++ b/v3/src/main/resources/data/postgresql/schema.sql @@ -386,7 +386,9 @@ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION getnexttrack(i_deviceid UUID) RETURNS TABLE( track CHARACTER VARYING - , methodid INTEGER) + , methodid INTEGER + , useridrecommended CHARACTER VARYING + , txtrecommendedinfo CHARACTER VARYING) AS ' DECLARE @@ -422,9 +424,11 @@ BEGIN -- Возвращаем trackid, конвертируя его в character varying и methodid RETURN QUERY SELECT - CAST((nexttrack.track) AS CHARACTER VARYING), - nexttrack.methodid - FROM getnexttrackid_v6(i_deviceid) AS nexttrack; + CAST((nexttrack.track) AS CHARACTER VARYING), + nexttrack.methodid, + CAST((nexttrack.useridrecommended) AS CHARACTER VARYING), + nexttrack.txtrecommendedinfo + FROM getnexttrackid_v8(i_deviceid) AS nexttrack; END; ' LANGUAGE plpgsql; @@ -834,7 +838,7 @@ BEGIN OR (SELECT iscensorial FROM tracks WHERE recid = trackid) != 0) - ORDER BY ratingsum DESC + ORDER BY ratingsum DESC, RANDOM() LIMIT 1; -- Если нашли что рекомендовать - выходим из функции IF found THEN @@ -922,3 +926,270 @@ BEGIN END; ' LANGUAGE plpgsql; + + +CREATE OR REPLACE FUNCTION getnexttrackid_v8(IN i_deviceid uuid) + RETURNS TABLE(track uuid, methodid integer, + useridrecommended uuid + , txtrecommendedinfo character varying + ) AS +' +DECLARE + i_userid UUID = i_deviceid; + rnd INTEGER = (SELECT trunc(random() * 1001)); + o_methodid INTEGER; -- id метода выбора трека + owntracks INTEGER; -- количество "своих" треков пользователя (обрезаем на 900 шт) + arrusers uuid ARRAY; -- массив пользователей для i_userid с неотрицательнымм коэффициентами схожести интересов + exceptusers uuid ARRAY; -- массив пользователей для i_userid с котороми не было пересечений по трекам +BEGIN + -- Выбираем следующий трек + + -- Определяем количество "своих" треков пользователя, ограничивая его 900 + owntracks = (SELECT COUNT(*) + FROM ( + SELECT * + FROM ratings + WHERE userid = i_userid + AND ratingsum >= 0 + LIMIT 900) AS count); + + -- Если rnd меньше количества "своих" треков, выбираем трек из треков пользователя (добавленных им или прослушанных до конца) + -- с положительным рейтингом, за исключением прослушанных за последние сутки + + IF (rnd < owntracks) + THEN + o_methodid = 2; -- метод выбора из своих треков + RETURN QUERY + SELECT + trackid, + o_methodid, + (SELECT CAST((null) AS UUID)), + (SELECT CAST((null) AS CHARACTER VARYING)) + FROM ratings + WHERE userid = i_userid + AND lastlisten < localtimestamp - INTERVAL ''1 day'' + AND ratingsum >= 0 + AND (SELECT isexist + FROM tracks + WHERE recid = trackid) = 1 + AND ((SELECT length + FROM tracks + WHERE recid = trackid) >= 120 + OR (SELECT length + FROM tracks + WHERE recid = trackid) IS NULL) + AND ((SELECT iscensorial + FROM tracks + WHERE recid = trackid) IS NULL + OR (SELECT iscensorial + FROM tracks + WHERE recid = trackid) != 0) + AND trackid NOT IN (SELECT trackid + FROM downloadtracks + WHERE reccreated > localtimestamp - INTERVAL ''1 day'') + ORDER BY RANDOM() + LIMIT 1; + + -- Если такой трек найден - выход из функции, возврат найденного значения + IF FOUND + THEN RETURN; + END IF; + END IF; + + -- Если rnd больше количества "своих" треков - рекомендуем трек из треков пользователя с наибольшим + -- коэффициентом схожести интересов и наибольшим рейтингом прослушивания + + -- Выберем всех пользователей с неотрицательным коэффициентом схожести интересов для i_userid + -- отсортировав по убыванию коэффициентов + arrusers = (SELECT ARRAY (SELECT CASE WHEN userid1 = i_userid THEN userid2 + WHEN userid2 = i_userid THEN userid1 + ELSE NULL + END + FROM ratios + WHERE userid1 = i_userid OR userid2 = i_userid AND ratio >= 0 + ORDER BY ratio DESC + )); + -- Выбираем пользователя i, с которым у него максимальный коэффициент. Среди его треков ищем трек + -- с максимальным рейтингом прослушивания, за исключением уже прослушанных пользователем i_userid. + -- Если рекомендовать нечего - берем следующего пользователя с наибольшим коэффициентом из оставшихся. + FOR i IN 1.. (SELECT COUNT (*) FROM unnest(arrusers)) LOOP + o_methodid = 4; -- метод выбора из рекомендованных треков + RETURN QUERY + SELECT + trackid, + o_methodid, + arrusers[i], + (SELECT CAST((ratio) AS CHARACTER VARYING) + FROM ratios + WHERE userid1 = i_userid AND userid2 = arrusers[i] OR userid2 = i_userid AND userid1 = arrusers[i]) + FROM ratings + WHERE userid = arrusers[i] + AND ratingsum > 0 + AND trackid NOT IN (SELECT trackid FROM ratings WHERE userid = i_userid) + AND trackid NOT IN (SELECT trackid + FROM downloadtracks + WHERE deviceid = i_deviceid + AND reccreated > localtimestamp - INTERVAL ''1 day'') + AND (SELECT isexist + FROM tracks + WHERE recid = trackid) = 1 + AND ((SELECT length + FROM tracks + WHERE recid = trackid) >= 120 + OR (SELECT length + FROM tracks + WHERE recid = trackid) IS NULL) + AND ((SELECT iscensorial + FROM tracks + WHERE recid = trackid) IS NULL + OR (SELECT iscensorial + FROM tracks + WHERE recid = trackid) != 0) + ORDER BY ratingsum DESC, RANDOM() + LIMIT 1; + -- Если нашли что рекомендовать - выходим из функции + IF found THEN + RETURN; + END IF; + END LOOP; + -- При отсутствии рекомендаций, выдавать случайный трек из непрослушанных треков с неотрицательным + -- рейтингом среди пользователей с которыми не было пересечений по трекам. + exceptusers = (SELECT ARRAY ( + SELECT * FROM ( + SELECT recid FROM users WHERE recid != i_userid + EXCEPT + (SELECT CASE WHEN userid1 = i_userid THEN userid2 + WHEN userid2 = i_userid THEN userid1 + ELSE NULL + END + FROM ratios WHERE userid1 = i_userid OR userid2 = i_userid) + ) AS us + ORDER BY RANDOM() + ) + ); + FOR i IN 1.. (SELECT COUNT (*) FROM unnest(exceptusers)) LOOP + o_methodid = 6; -- метод выбора из непрослушанных треков с неотрицательным рейтингом среди пользователей с которыми не было пересечений + RETURN QUERY + SELECT + recid, + o_methodid, + exceptusers[i], + (SELECT CAST((''рекомендовано от пользователя с которым не было пересечений'') AS CHARACTER VARYING)) + FROM tracks + WHERE recid IN (SELECT trackid FROM ratings WHERE userid = exceptusers[i] AND ratingsum >= 0) + AND recid NOT IN (SELECT trackid FROM ratings WHERE userid = i_userid) + AND isexist = 1 + AND (iscensorial IS NULL OR iscensorial != 0) + AND (length > 120 OR length IS NULL) + AND recid NOT IN (SELECT trackid + FROM downloadtracks + WHERE reccreated > localtimestamp - INTERVAL ''1 day'') + ORDER BY RANDOM() + LIMIT 1; + -- Если нашли что рекомендовать - выходим из функции + IF found THEN + RETURN; + ELSE + + END IF; + END LOOP; + + -- Если таких треков нет - выбираем случайный трек из ни разу не прослушанных пользователем треков + o_methodid = 3; -- метод выбора из непрослушанных треков + RETURN QUERY + SELECT + recid, + o_methodid, + (SELECT CAST((null) AS UUID)), + (SELECT CAST((null) AS CHARACTER VARYING)) + FROM tracks + WHERE recid NOT IN + (SELECT trackid + FROM ratings + WHERE userid = i_userid) + AND isexist = 1 + AND (iscensorial IS NULL OR iscensorial != 0) + AND (length > 120 OR length IS NULL) + AND recid NOT IN (SELECT trackid + FROM downloadtracks + WHERE reccreated > localtimestamp - INTERVAL ''1 day'') + ORDER BY RANDOM() + LIMIT 1; + + -- Если такой трек найден - выход из функции, возврат найденного значения + IF FOUND + THEN RETURN; + END IF; + + -- Если предыдущие запросы вернули null, выбираем случайный трек + o_methodid = 1; -- метод выбора случайного трека + RETURN QUERY + SELECT + recid, + o_methodid, + (SELECT CAST((null) AS UUID)), + (SELECT CAST((null) AS CHARACTER VARYING)) + FROM tracks + WHERE isexist = 1 + AND (iscensorial IS NULL OR iscensorial != 0) + AND (length > 120 OR length IS NULL) + ORDER BY RANDOM() + LIMIT 1; + RETURN; +END; +' +LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION getusersrating(IN i_count integer) + RETURNS TABLE(tuserid character varying, treccreated character varying, trecname character varying, trecupdated character varying, towntracks bigint, tlasttracks bigint) AS +' + +BEGIN + IF i_count < 0 THEN + i_count = null; + END IF; +RETURN QUERY SELECT CAST((res1.recid) AS CHARACTER VARYING), CAST((res1.reccreated) AS CHARACTER VARYING), res1.recname, CAST((res1.recupdated) AS CHARACTER VARYING), res1.owntracks, COUNT(res2.userid) AS lasttracks +FROM + (SELECT u.recid, u.reccreated, u.recname, u.recupdated, COUNT(r.recid) AS owntracks + FROM users u + LEFT OUTER JOIN ratings r ON u.recid = r.userid + GROUP BY u.recid) res1 + LEFT OUTER JOIN (SELECT d.reccreated, dev.userid FROM downloadtracks d + INNER JOIN devices dev + ON dev.recid= d.deviceid AND d.reccreated > localtimestamp - INTERVAL ''1 day'') res2 + ON res2.userid = res1.recid + GROUP BY res1.recid, res1.reccreated, res1.recname, res1.recupdated, res1.owntracks + ORDER BY lasttracks DESC, owntracks DESC + LIMIT i_count; + +END; +' +LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION getlasttracks( + IN i_deviceid uuid, + IN i_count integer) + RETURNS TABLE(recid uuid, reccreated timestamp without time zone, recname character varying, recupdated timestamp without time zone, deviceid uuid, trackid uuid, methodid integer, txtrecommendinfo character varying, userrecommend uuid) AS +' +BEGIN + IF i_count < 0 THEN + i_count = null; + END IF; +RETURN QUERY SELECT * + FROM downloadtracks + WHERE downloadtracks.deviceid = i_deviceid + ORDER BY downloadtracks.reccreated DESC + LIMIT i_count; +END; +' +LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION getuserdevices(IN i_userid uuid) + RETURNS TABLE(recid uuid, reccreated timestamp without time zone, recname character varying, recupdated timestamp without time zone, userid uuid) AS +' +BEGIN + RETURN QUERY + SELECT * FROM devices WHERE userid = i_userid; +END; +' +LANGUAGE plpgsql \ No newline at end of file From bde67d403c370a4acd3af0b8de54aee763d4c349 Mon Sep 17 00:00:00 2001 From: Alexandra Polunina Date: Mon, 27 Mar 2017 16:40:29 +0300 Subject: [PATCH 30/44] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8?= =?UTF-8?q?=D0=BB=D0=B0=20=D0=B2=20=D0=BA=D0=BE=D0=BD=D1=82=D1=80=D0=BE?= =?UTF-8?q?=D0=BB=D0=BB=D0=B5=D1=80=20v3=20=D1=81=D0=BE=D1=85=D1=80=D0=B0?= =?UTF-8?q?=D0=BD=D0=B5=D0=BD=D0=B8=D0=B5=20=D1=81=D1=82=D0=B0=D1=82=D0=B8?= =?UTF-8?q?=D1=81=D1=82=D0=B8=D0=BA=D0=B8,=20=D0=BF=D0=BE=D0=BF=D1=80?= =?UTF-8?q?=D0=B0=D0=B2=D0=B8=D0=BB=D0=B0=20=D1=85=D1=80=D0=B0=D0=BD=D0=B8?= =?UTF-8?q?=D0=BC=D0=BA=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- v3/src/main/java/ownradio/web/rest/v3/TrackController.java | 2 ++ v3/src/main/resources/data/postgresql/schema.sql | 6 +++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/v3/src/main/java/ownradio/web/rest/v3/TrackController.java b/v3/src/main/java/ownradio/web/rest/v3/TrackController.java index 6c8546b..ca7dd13 100644 --- a/v3/src/main/java/ownradio/web/rest/v3/TrackController.java +++ b/v3/src/main/java/ownradio/web/rest/v3/TrackController.java @@ -144,6 +144,8 @@ public ResponseEntity getNextTrack(@PathVariable UUID deviceId) { downloadTrack.setTrack(track); downloadTrack.setDevice(device); downloadTrack.setMethodid(nextTrack.getMethodid()); + downloadTrack.setUserrecommend(nextTrack.getUseridrecommended()); + downloadTrack.setTxtrecommendinfo(nextTrack.getTxtrecommendedinfo()); downloadTrackRepository.saveAndFlush(downloadTrack); log.info("getNextTrack return {} {}", nextTrack.getMethodid().toString(), trackInfo.get("id")); return new ResponseEntity<>(trackInfo, HttpStatus.OK); diff --git a/v3/src/main/resources/data/postgresql/schema.sql b/v3/src/main/resources/data/postgresql/schema.sql index 9032def..f117d84 100644 --- a/v3/src/main/resources/data/postgresql/schema.sql +++ b/v3/src/main/resources/data/postgresql/schema.sql @@ -462,7 +462,7 @@ BEGIN IF NOT FOUND THEN EXIT; -- если данных нет - выходим END IF; -- если для данной пары пользователей уже записан коэффициент - пропускаем, иначе - записываем во временную таблицу - IF NOT EXISTS (SELECT * FROM temp_ratio WHERE userid1 = cuser2 AND userid2 = cuser1) THEN + IF NOT EXISTS (SELECT * FROM temp_ratio WHERE userid1 = cuser2 AND userid2 = cuser1 OR userid1 = cuser1 AND userid2 = cuser2) THEN INSERT INTO temp_ratio(userid1, userid2, ratio) VALUES (cuser1, cuser2, cratio); END IF; @@ -517,7 +517,7 @@ BEGIN IF NOT FOUND THEN EXIT; -- если данных нет - выходим END IF; -- если для данной пары пользователей уже записан коэффициент - пропускаем, иначе - записываем во временную таблицу - IF NOT EXISTS (SELECT * FROM temp_ratio WHERE userid1 = cuser2 AND userid2 = cuser1) THEN + IF NOT EXISTS (SELECT * FROM temp_ratio WHERE userid1 = cuser2 AND userid2 = cuser1 OR userid1 = cuser1 AND userid2 = cuser2) THEN INSERT INTO temp_ratio(userid1, userid2, ratio) VALUES (cuser1, cuser2, cratio); END IF; @@ -1021,7 +1021,7 @@ BEGIN arrusers[i], (SELECT CAST((ratio) AS CHARACTER VARYING) FROM ratios - WHERE userid1 = i_userid AND userid2 = arrusers[i] OR userid2 = i_userid AND userid1 = arrusers[i]) + WHERE userid1 = i_userid AND userid2 = arrusers[i] OR userid2 = i_userid AND userid1 = arrusers[i] LIMIT 1) FROM ratings WHERE userid = arrusers[i] AND ratingsum > 0 From b3bd59068154c5492815475c3b91fc61c7647fb6 Mon Sep 17 00:00:00 2001 From: Alexandra Polunina Date: Wed, 29 Mar 2017 19:08:57 +0300 Subject: [PATCH 31/44] =?UTF-8?q?=D0=9F=D0=B5=D1=80=D0=B5=D0=BD=D0=B5?= =?UTF-8?q?=D1=81=D0=BB=D0=B0=20=D1=81=D0=BE=D1=85=D1=80=D0=B0=D0=BD=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=20=D0=B8=D0=BD=D1=84=D0=BE=D1=80=D0=BC=D0=B0?= =?UTF-8?q?=D1=86=D0=B8=D0=B8=20=D0=BE=20=D0=B2=D1=8B=D0=B4=D0=B0=D0=BD?= =?UTF-8?q?=D0=BD=D1=8B=D1=85=20=D1=82=D1=80=D0=B5=D0=BA=D0=B0=D1=85=20?= =?UTF-8?q?=D0=B2=20=D1=85=D1=80=D0=B0=D0=BD=D0=B8=D0=BC=D1=83=D1=8E=20?= =?UTF-8?q?=D0=BF=D1=80=D0=BE=D1=86=D0=B5=D0=B4=D1=83=D1=80=D1=83=20=D0=B4?= =?UTF-8?q?=D0=BB=D1=8F=20=D1=83=D1=81=D0=BA=D0=BE=D1=80=D0=B5=D0=BD=D0=B8?= =?UTF-8?q?=D1=8F=20=D1=81=D0=BE=D1=85=D1=80=D0=B0=D0=BD=D0=B5=D0=BD=D0=B8?= =?UTF-8?q?=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ownradio/web/rest/v3/TrackController.java | 21 +- .../ownradio/web/rest/v4/TrackController.java | 24 +- .../main/resources/data/postgresql/schema.sql | 232 +++++++++++++++++- 3 files changed, 250 insertions(+), 27 deletions(-) diff --git a/v3/src/main/java/ownradio/web/rest/v3/TrackController.java b/v3/src/main/java/ownradio/web/rest/v3/TrackController.java index ca7dd13..13bf78d 100644 --- a/v3/src/main/java/ownradio/web/rest/v3/TrackController.java +++ b/v3/src/main/java/ownradio/web/rest/v3/TrackController.java @@ -108,6 +108,16 @@ public ResponseEntity getNextTrack(@PathVariable UUID deviceId) { if (nextTrack != null) { try { Track track = trackRepository.findOne(nextTrack.getTrackid()); + //Сохраняем информацию об отданном треке +// Device device = new Device(); +// device.setRecid(deviceId); +// DownloadTrack downloadTrack = new DownloadTrack(); +// downloadTrack.setTrack(track); +// downloadTrack.setDevice(device); +// downloadTrack.setMethodid(nextTrack.getMethodid()); +// downloadTrack.setUserrecommend(nextTrack.getUseridrecommended()); +// downloadTrack.setTxtrecommendinfo(nextTrack.getTxtrecommendedinfo()); +// downloadTrackRepository.saveAndFlush(downloadTrack); File file = new File(track.getPath()); if(!file.exists()){ @@ -136,17 +146,6 @@ public ResponseEntity getNextTrack(@PathVariable UUID deviceId) { trackInfo.put("artist", "Unknown artist"); trackInfo.put("methodid", nextTrack.getMethodid().toString()); - - //Сохраняем информацию об отданном треке - Device device = new Device(); - device.setRecid(deviceId); - DownloadTrack downloadTrack = new DownloadTrack(); - downloadTrack.setTrack(track); - downloadTrack.setDevice(device); - downloadTrack.setMethodid(nextTrack.getMethodid()); - downloadTrack.setUserrecommend(nextTrack.getUseridrecommended()); - downloadTrack.setTxtrecommendinfo(nextTrack.getTxtrecommendedinfo()); - downloadTrackRepository.saveAndFlush(downloadTrack); log.info("getNextTrack return {} {}", nextTrack.getMethodid().toString(), trackInfo.get("id")); return new ResponseEntity<>(trackInfo, HttpStatus.OK); }catch (Exception ex){ diff --git a/v3/src/main/java/ownradio/web/rest/v4/TrackController.java b/v3/src/main/java/ownradio/web/rest/v4/TrackController.java index 3036e8e..98993d8 100644 --- a/v3/src/main/java/ownradio/web/rest/v4/TrackController.java +++ b/v3/src/main/java/ownradio/web/rest/v4/TrackController.java @@ -107,10 +107,21 @@ public ResponseEntity getNextTrack(@PathVariable UUID deviceId) { } + Map trackInfo = new HashMap<>(); if (nextTrack != null) { try { Track track = trackRepository.findOne(nextTrack.getTrackid()); + //Сохраняем информацию об отданном треке +// Device device = new Device(); +// device.setRecid(deviceId); +// DownloadTrack downloadTrack = new DownloadTrack(); +// downloadTrack.setTrack(track); +// downloadTrack.setDevice(device); +// downloadTrack.setMethodid(nextTrack.getMethodid()); +// downloadTrack.setUserrecommend(nextTrack.getUseridrecommended()); +// downloadTrack.setTxtrecommendinfo(nextTrack.getTxtrecommendedinfo()); +// downloadTrackRepository.saveAndFlush(downloadTrack); File file = new File(track.getPath()); if(!file.exists()){ @@ -138,17 +149,8 @@ public ResponseEntity getNextTrack(@PathVariable UUID deviceId) { else trackInfo.put("artist", "Unknown artist"); - //Сохраняем информацию об отданном треке - Device device = new Device(); - device.setRecid(deviceId); - - DownloadTrack downloadTrack = new DownloadTrack(); - downloadTrack.setTrack(track); - downloadTrack.setDevice(device); - downloadTrack.setMethodid(nextTrack.getMethodid()); - downloadTrack.setUserrecommend(nextTrack.getUseridrecommended()); - downloadTrack.setTxtrecommendinfo(nextTrack.getTxtrecommendedinfo()); - downloadTrackRepository.saveAndFlush(downloadTrack); + + log.info("getNextTrack return {} {}", nextTrack.getMethodid(), trackInfo.get("id")); return new ResponseEntity<>(trackInfo, HttpStatus.OK); }catch (Exception ex){ diff --git a/v3/src/main/resources/data/postgresql/schema.sql b/v3/src/main/resources/data/postgresql/schema.sql index f117d84..0e79a96 100644 --- a/v3/src/main/resources/data/postgresql/schema.sql +++ b/v3/src/main/resources/data/postgresql/schema.sql @@ -428,7 +428,7 @@ BEGIN nexttrack.methodid, CAST((nexttrack.useridrecommended) AS CHARACTER VARYING), nexttrack.txtrecommendedinfo - FROM getnexttrackid_v8(i_deviceid) AS nexttrack; + FROM getnexttrackid_v10(i_deviceid) AS nexttrack; END; ' LANGUAGE plpgsql; @@ -802,8 +802,7 @@ BEGIN ELSE NULL END FROM ratios - WHERE userid1 = i_userid OR userid2 = i_userid - AND ratio >= 0 + WHERE userid1 = (i_userid OR userid2 = i_userid) AND ratio >= 0 ORDER BY ratio DESC )); -- Выбираем пользователя i, с которым у него максимальный коэффициент. Среди его треков ищем трек @@ -1006,7 +1005,7 @@ BEGIN ELSE NULL END FROM ratios - WHERE userid1 = i_userid OR userid2 = i_userid AND ratio >= 0 + WHERE (userid1 = i_userid OR userid2 = i_userid) AND ratio >= 0 ORDER BY ratio DESC )); -- Выбираем пользователя i, с которым у него максимальный коэффициент. Среди его треков ищем трек @@ -1192,4 +1191,227 @@ BEGIN SELECT * FROM devices WHERE userid = i_userid; END; ' -LANGUAGE plpgsql \ No newline at end of file +LANGUAGE plpgsql; + + +CREATE OR REPLACE FUNCTION getnexttrackid_v10(IN i_deviceid uuid) + RETURNS TABLE(track uuid, methodid integer, useridrecommended uuid, txtrecommendedinfo character varying) AS +' +DECLARE + i_userid UUID = i_deviceid; --пока не реалезовано объединение пользователей - гуиды одинаковые + rnd INTEGER = (SELECT trunc(random() * 1001)); -- генерируем случайное целое число в диапазоне от 1 до 1000 + o_methodid INTEGER; -- id метода выбора трека + owntracks INTEGER; -- количество "своих" треков пользователя (обрезаем на 900 шт) + arrusers uuid ARRAY; -- массив пользователей для i_userid с неотрицательнымм коэффициентами схожести интересов + exceptusers uuid ARRAY; -- массив пользователей для i_userid с котороми не было пересечений по трекам +BEGIN + DROP TABLE IF EXISTS temp_track; + CREATE TEMP TABLE temp_track(track uuid, methodid integer, useridrecommended uuid, txtrecommendedinfo character varying); + + -- Выбираем следующий трек + + -- Определяем количество "своих" треков пользователя, ограничивая его 900 + owntracks = (SELECT COUNT(*) + FROM ( + SELECT * + FROM ratings + WHERE userid = i_userid + AND ratingsum >= 0 + LIMIT 900) AS count); + + -- Если rnd меньше количества "своих" треков, выбираем трек из треков пользователя (добавленных им или прослушанных до конца) + -- с положительным рейтингом, за исключением прослушанных за последние сутки + + IF (rnd < owntracks) + THEN + o_methodid = 2; -- метод выбора из своих треков + INSERT INTO temp_track ( + SELECT + trackid, + o_methodid, + (SELECT CAST((null) AS UUID)), + (SELECT CAST((null) AS CHARACTER VARYING)) + FROM ratings + WHERE userid = i_userid + AND lastlisten < localtimestamp - INTERVAL ''1 day'' + AND ratingsum >= 0 + AND (SELECT isexist + FROM tracks + WHERE recid = trackid) = 1 + AND ((SELECT length + FROM tracks + WHERE recid = trackid) >= 120 + OR (SELECT length + FROM tracks + WHERE recid = trackid) IS NULL) + AND ((SELECT iscensorial + FROM tracks + WHERE recid = trackid) IS NULL + OR (SELECT iscensorial + FROM tracks + WHERE recid = trackid) != 0) + AND trackid NOT IN (SELECT trackid + FROM downloadtracks + WHERE reccreated > localtimestamp - INTERVAL ''1 day'') + ORDER BY RANDOM() + LIMIT 1); + + -- Если такой трек найден - выход из функции, возврат найденного значения + IF FOUND THEN + INSERT INTO downloadtracks (SELECT uuid_generate_v4(),now(),null, null, i_userid, temp_track.track AS trackid, temp_track.methodid AS methodid, temp_track.txtrecommendedinfo AS txtrecommendinfo, temp_track.useridrecommended AS userrecommend FROM temp_track); + RETURN QUERY SELECT * FROM temp_track; + RETURN; + END IF; + END IF; + + -- Если rnd больше количества "своих" треков - рекомендуем трек из треков пользователя с наибольшим + -- коэффициентом схожести интересов и наибольшим рейтингом прослушивания + + -- Выберем всех пользователей с неотрицательным коэффициентом схожести интересов для i_userid + -- отсортировав по убыванию коэффициентов + arrusers = (SELECT ARRAY (SELECT CASE WHEN userid1 = i_userid THEN userid2 + WHEN userid2 = i_userid THEN userid1 + ELSE NULL + END + FROM ratios + WHERE (userid1 = i_userid OR userid2 = i_userid) AND ratio >= 0 + ORDER BY ratio DESC + )); + -- Выбираем пользователя i, с которым у него максимальный коэффициент. Среди его треков ищем трек + -- с максимальным рейтингом прослушивания, за исключением уже прослушанных пользователем i_userid. + -- Если рекомендовать нечего - берем следующего пользователя с наибольшим коэффициентом из оставшихся. + FOR i IN 1.. (SELECT COUNT (*) FROM unnest(arrusers)) LOOP + o_methodid = 4; -- метод выбора из рекомендованных треков + INSERT INTO temp_track ( + SELECT + trackid, + o_methodid, + arrusers[i], + (SELECT CAST ((concat(''Коэффициент схожести '', ratio)) AS CHARACTER VARYING) + FROM ratios + WHERE userid1 = i_userid AND userid2 = arrusers[i] OR userid2 = i_userid AND userid1 = arrusers[i] LIMIT 1) + FROM ratings + WHERE userid = arrusers[i] + AND ratingsum > 0 + AND trackid NOT IN (SELECT trackid FROM ratings WHERE userid = i_userid) + AND trackid NOT IN (SELECT trackid + FROM downloadtracks + WHERE deviceid = i_deviceid + AND reccreated > localtimestamp - INTERVAL ''1 day'') + AND (SELECT isexist + FROM tracks + WHERE recid = trackid) = 1 + AND ((SELECT length + FROM tracks + WHERE recid = trackid) >= 120 + OR (SELECT length + FROM tracks + WHERE recid = trackid) IS NULL) + AND ((SELECT iscensorial + FROM tracks + WHERE recid = trackid) IS NULL + OR (SELECT iscensorial + FROM tracks + WHERE recid = trackid) != 0) + ORDER BY ratingsum DESC, RANDOM() + LIMIT 1); + -- Если нашли что рекомендовать - выходим из функции + IF found THEN + INSERT INTO downloadtracks (SELECT uuid_generate_v4(),now(),null, null, i_userid, temp_track.track AS trackid, temp_track.methodid AS methodid, temp_track.txtrecommendedinfo AS txtrecommendinfo, temp_track.useridrecommended AS userrecommend FROM temp_track); + RETURN QUERY SELECT * FROM temp_track; + RETURN; + END IF; + END LOOP; + -- При отсутствии рекомендаций, выдавать случайный трек из непрослушанных треков с неотрицательным + -- рейтингом среди пользователей с которыми не было пересечений по трекам. + exceptusers = (SELECT ARRAY ( + SELECT * FROM ( + SELECT recid FROM users WHERE recid != i_userid + EXCEPT + (SELECT CASE WHEN userid1 = i_userid THEN userid2 + WHEN userid2 = i_userid THEN userid1 + ELSE NULL + END + FROM ratios WHERE userid1 = i_userid OR userid2 = i_userid) + ) AS us + ORDER BY RANDOM() + ) + ); + FOR i IN 1.. (SELECT COUNT (*) FROM unnest(exceptusers)) LOOP + o_methodid = 6; -- метод выбора из непрослушанных треков с неотрицательным рейтингом среди пользователей с которыми не было пересечений + INSERT INTO temp_track ( + SELECT + recid, + o_methodid, + exceptusers[i], + (SELECT CAST((''рекомендовано от пользователя с которым не было пересечений'') AS CHARACTER VARYING)) + FROM tracks + WHERE recid IN (SELECT trackid FROM ratings WHERE userid = exceptusers[i] AND ratingsum >= 0) + AND recid NOT IN (SELECT trackid FROM ratings WHERE userid = i_userid) + AND isexist = 1 + AND (iscensorial IS NULL OR iscensorial != 0) + AND (length > 120 OR length IS NULL) + AND recid NOT IN (SELECT trackid + FROM downloadtracks + WHERE reccreated > localtimestamp - INTERVAL ''1 day'') + ORDER BY RANDOM() + LIMIT 1); + -- Если нашли что рекомендовать - выходим из функции + IF found THEN + INSERT INTO downloadtracks (SELECT uuid_generate_v4(),now(),null, null, i_userid, temp_track.track AS trackid, temp_track.methodid AS methodid, temp_track.txtrecommendedinfo AS txtrecommendinfo, temp_track.useridrecommended AS userrecommend FROM temp_track); + RETURN QUERY SELECT * FROM temp_track; + RETURN; + ELSE + + END IF; + END LOOP; + + -- Если таких треков нет - выбираем случайный трек из ни разу не прослушанных пользователем треков + o_methodid = 3; -- метод выбора из непрослушанных треков + INSERT INTO temp_track ( + SELECT + recid, + o_methodid, + (SELECT CAST((null) AS UUID)), + (SELECT CAST((null) AS CHARACTER VARYING)) + FROM tracks + WHERE recid NOT IN + (SELECT trackid + FROM ratings + WHERE userid = i_userid) + AND isexist = 1 + AND (iscensorial IS NULL OR iscensorial != 0) + AND (length > 120 OR length IS NULL) + AND recid NOT IN (SELECT trackid + FROM downloadtracks + WHERE reccreated > localtimestamp - INTERVAL ''1 day'') + ORDER BY RANDOM() + LIMIT 1); + + -- Если такой трек найден - выход из функции, возврат найденного значения + IF FOUND THEN + INSERT INTO downloadtracks (SELECT uuid_generate_v4(),now(),null, null, i_userid, temp_track.track AS trackid, temp_track.methodid AS methodid, temp_track.txtrecommendedinfo AS txtrecommendinfo, temp_track.useridrecommended AS userrecommend FROM temp_track); + RETURN QUERY SELECT * FROM temp_track; + RETURN; + END IF; + + -- Если предыдущие запросы вернули null, выбираем случайный трек + o_methodid = 1; -- метод выбора случайного трека + INSERT INTO temp_track ( + SELECT + recid, + o_methodid, + (SELECT CAST((null) AS UUID)), + (SELECT CAST((null) AS CHARACTER VARYING)) + FROM tracks + WHERE isexist = 1 + AND (iscensorial IS NULL OR iscensorial != 0) + AND (length > 120 OR length IS NULL) + ORDER BY RANDOM() + LIMIT 1); + INSERT INTO downloadtracks (SELECT uuid_generate_v4(),now(),null, null, i_userid, temp_track.track AS trackid, temp_track.methodid AS methodid, temp_track.txtrecommendedinfo AS txtrecommendinfo, temp_track.useridrecommended AS userrecommend FROM temp_track); + RETURN QUERY SELECT * FROM temp_track; + RETURN; +END; +' +LANGUAGE plpgsql; \ No newline at end of file From bb72e9a05b7e9fafb5c7653c623b664d0293e79b Mon Sep 17 00:00:00 2001 From: Alexandra Polunina Date: Mon, 10 Apr 2017 12:03:08 +0300 Subject: [PATCH 32/44] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8?= =?UTF-8?q?=D0=BB=D0=B0=20=D0=B0=D1=80=D0=B8=20=D1=80=D0=B5=D0=B3=D0=B8?= =?UTF-8?q?=D1=81=D1=82=D1=80=D0=B0=D1=86=D0=B8=D0=B8=20=D1=83=D1=81=D1=82?= =?UTF-8?q?=D1=80=D0=BE=D0=B9=D1=81=D1=82=D0=B2=20=D0=B8=20=D0=B2=D1=8B?= =?UTF-8?q?=D0=B4=D0=B0=D1=87=D0=B8=20=D0=BF=D0=BE=D1=81=D0=BB=D0=B5=D0=B4?= =?UTF-8?q?=D0=BD=D0=B8=D1=85=20=D0=B0=D0=BA=D1=82=D0=B8=D0=B2=D0=BD=D1=8B?= =?UTF-8?q?=D1=85=20=D1=83=D1=81=D1=82=D1=80=D0=BE=D0=B9=D1=81=D1=82=D0=B2?= =?UTF-8?q?=20=D0=B8=20=D0=B8=D1=81=D1=82=D0=BE=D1=80=D0=B8=D0=B8=20=D1=82?= =?UTF-8?q?=D1=80=D0=B5=D0=BA=D0=BE=D0=B2=20=D0=B4=D0=BB=D1=8F=20=D0=BD?= =?UTF-8?q?=D0=B8=D1=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 9 + .../java/ownradio/domain/TracksHistory.java | 18 + .../java/ownradio/domain/UsersRating.java | 2 - .../ownradio/repository/DeviceRepository.java | 5 + .../repository/DownloadTrackRepository.java | 4 + .../ownradio/repository/TrackRepository.java | 2 +- .../java/ownradio/service/DeviceService.java | 2 + .../service/DownloadTrackService.java | 3 + .../service/impl/DeviceServiceImpl.java | 14 + .../impl/DownloadTrackServiceImpl.java | 26 +- .../web/rest/v4/DeviceController.java | 47 ++ .../web/rest/v4/HistoryController.java | 2 +- .../web/rest/v4/StatisticsController.java | 22 + .../main/resources/data/postgresql/schema.sql | 746 +++--------------- 14 files changed, 277 insertions(+), 625 deletions(-) create mode 100644 v3/src/main/java/ownradio/domain/TracksHistory.java create mode 100644 v3/src/main/java/ownradio/web/rest/v4/DeviceController.java diff --git a/README.md b/README.md index 2d11628..ad3d9ab 100644 --- a/README.md +++ b/README.md @@ -71,6 +71,14 @@ Content-Type →application/json;charset=UTF-8 Web API v4 --- +### Регистрация нового устройства +##### GET /v4/devices/{deviceId}/{deviceName}/registerdevice +* `{deviceId}` - UUID устройства +* `{deviceName}` - название устройства (может отсутсвтваовать) +##### HttpStatus +* `200, "OK"` – если все ок +* `400, "Bad Request"` - Если пользователь ввел некорректные данные + ### Получение следующего трека с сервера ##### GET /v4/tracks/{deviceId}/next @@ -94,4 +102,5 @@ Content-Type →application/json;charset=UTF-8 ##### HttpStatus * `200, "OK"` – если все ок +* `404, "Not found"` - если deviceId или trackid не найден * `500, "Internal Server Error"` – если произошел сбой на сервере diff --git a/v3/src/main/java/ownradio/domain/TracksHistory.java b/v3/src/main/java/ownradio/domain/TracksHistory.java new file mode 100644 index 0000000..bce7faa --- /dev/null +++ b/v3/src/main/java/ownradio/domain/TracksHistory.java @@ -0,0 +1,18 @@ +package ownradio.domain; + +import lombok.Getter; +import lombok.Setter; + +/** + * + * Сущность для хранения информации об отданных пользователю треках и истории по ним + * + * Created by a.polunina on 07.04.2017. + */ +@Setter +@Getter +public class TracksHistory { + private DownloadTrack downloadTrack; + private History history; + +} diff --git a/v3/src/main/java/ownradio/domain/UsersRating.java b/v3/src/main/java/ownradio/domain/UsersRating.java index 6cedcae..2d2f25d 100644 --- a/v3/src/main/java/ownradio/domain/UsersRating.java +++ b/v3/src/main/java/ownradio/domain/UsersRating.java @@ -4,9 +4,7 @@ import lombok.Setter; import org.springframework.format.annotation.DateTimeFormat; -import javax.persistence.TemporalType; import java.math.BigInteger; -import java.util.Calendar; import java.util.UUID; /** diff --git a/v3/src/main/java/ownradio/repository/DeviceRepository.java b/v3/src/main/java/ownradio/repository/DeviceRepository.java index d25a186..0a99760 100644 --- a/v3/src/main/java/ownradio/repository/DeviceRepository.java +++ b/v3/src/main/java/ownradio/repository/DeviceRepository.java @@ -16,4 +16,9 @@ public interface DeviceRepository extends JpaRepository { @Query(value = "select * from devices where userid = ?1", nativeQuery = true) List getUserDevices(UUID userid); + @Query(value = "select * from registerdevice(?1, ?2)", nativeQuery = true) + boolean registerdevice(UUID deviceId, String deviceName); + + @Query(value = "select * from getlastdevices()", nativeQuery = true) + List getLastDevices(); } diff --git a/v3/src/main/java/ownradio/repository/DownloadTrackRepository.java b/v3/src/main/java/ownradio/repository/DownloadTrackRepository.java index 17e4c0d..a42b608 100644 --- a/v3/src/main/java/ownradio/repository/DownloadTrackRepository.java +++ b/v3/src/main/java/ownradio/repository/DownloadTrackRepository.java @@ -20,4 +20,8 @@ public interface DownloadTrackRepository extends JpaRepository getLastTracksByDevice(UUID deviceid, Integer countTracks); + + @Query(value = "select * from getTracksHistoryByDevice(?1, ?2)", nativeQuery = true) + List getTracksHistoryByDevice(UUID deviceId, Integer countRows); + } diff --git a/v3/src/main/java/ownradio/repository/TrackRepository.java b/v3/src/main/java/ownradio/repository/TrackRepository.java index e525152..db79e06 100644 --- a/v3/src/main/java/ownradio/repository/TrackRepository.java +++ b/v3/src/main/java/ownradio/repository/TrackRepository.java @@ -16,8 +16,8 @@ public interface TrackRepository extends JpaRepository { @Query(value = "select getnexttrackid_string(?1)", nativeQuery = true) UUID getNextTrackId(UUID deviceId); +// @Query(value = "select * from getnexttrack_v2(?1)", nativeQuery = true) @Query(value = "select * from getnexttrack(?1)", nativeQuery = true) -// @Query(value = "select * from getnexttrackid_v2(?1)", nativeQuery = true) List getNextTrackV2(UUID deviceId); @Query(value = "select registertrack(?1, ?2, ?3, ?4)", nativeQuery = true) diff --git a/v3/src/main/java/ownradio/service/DeviceService.java b/v3/src/main/java/ownradio/service/DeviceService.java index f4787b4..40321c5 100644 --- a/v3/src/main/java/ownradio/service/DeviceService.java +++ b/v3/src/main/java/ownradio/service/DeviceService.java @@ -17,4 +17,6 @@ public interface DeviceService { Device getById(UUID uuid); List getByUserid(UUID userid); + + List getLastDevices(); } diff --git a/v3/src/main/java/ownradio/service/DownloadTrackService.java b/v3/src/main/java/ownradio/service/DownloadTrackService.java index ea316b3..79905ae 100644 --- a/v3/src/main/java/ownradio/service/DownloadTrackService.java +++ b/v3/src/main/java/ownradio/service/DownloadTrackService.java @@ -1,6 +1,7 @@ package ownradio.service; import ownradio.domain.DownloadTrack; +import ownradio.domain.TracksHistory; import java.util.List; import java.util.UUID; @@ -14,4 +15,6 @@ public interface DownloadTrackService { void save(DownloadTrack downloadTrack); List getLastTracksByDevice(UUID deviceId, Integer countTracks); + + List getTracksHistoryByDevice(UUID deviceId, Integer countRows); } diff --git a/v3/src/main/java/ownradio/service/impl/DeviceServiceImpl.java b/v3/src/main/java/ownradio/service/impl/DeviceServiceImpl.java index a70492a..5a1631d 100644 --- a/v3/src/main/java/ownradio/service/impl/DeviceServiceImpl.java +++ b/v3/src/main/java/ownradio/service/impl/DeviceServiceImpl.java @@ -3,9 +3,11 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import ownradio.domain.Device; +import ownradio.domain.UsersRating; import ownradio.repository.DeviceRepository; import ownradio.service.DeviceService; +import java.util.ArrayList; import java.util.List; import java.util.UUID; @@ -34,4 +36,16 @@ public Device getById(UUID uuid) { public List getByUserid(UUID uuid) { return deviceRepository.getUserDevices(uuid); } + + @Override + public List getLastDevices(){ + List lastDevices = new ArrayList(); + List objects = deviceRepository.getLastDevices(); + if (objects != null) { + for (int i = 0; i < objects.size(); i++) { + lastDevices.add(getById(UUID.fromString((String) objects.get(i)))); + } + } + return lastDevices; + } } diff --git a/v3/src/main/java/ownradio/service/impl/DownloadTrackServiceImpl.java b/v3/src/main/java/ownradio/service/impl/DownloadTrackServiceImpl.java index 37f4714..fdaec20 100644 --- a/v3/src/main/java/ownradio/service/impl/DownloadTrackServiceImpl.java +++ b/v3/src/main/java/ownradio/service/impl/DownloadTrackServiceImpl.java @@ -4,9 +4,12 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import ownradio.domain.DownloadTrack; +import ownradio.domain.TracksHistory; import ownradio.repository.DownloadTrackRepository; +import ownradio.repository.HistoryRepository; import ownradio.service.DownloadTrackService; +import java.util.ArrayList; import java.util.List; import java.util.UUID; @@ -14,10 +17,12 @@ public class DownloadTrackServiceImpl implements DownloadTrackService { private final DownloadTrackRepository downloadTrackRepository; + private HistoryRepository historyRepository; @Autowired - public DownloadTrackServiceImpl(DownloadTrackRepository downloadTrackRepository) { + public DownloadTrackServiceImpl(DownloadTrackRepository downloadTrackRepository, HistoryRepository historyRepository) { this.downloadTrackRepository = downloadTrackRepository; + this.historyRepository = historyRepository; } @Override @@ -30,4 +35,23 @@ public void save(DownloadTrack downloadTrack) { public List getLastTracksByDevice(UUID deviceId, Integer countTracks) { return downloadTrackRepository.getLastTracksByDevice(deviceId, countTracks); } + + @Override + @Transactional + public List getTracksHistoryByDevice(UUID deviceId, Integer countRows){ + List tracksHistories = new ArrayList(); + List objects = downloadTrackRepository.getTracksHistoryByDevice(deviceId, countRows); + if (objects != null) { + for (int i = 0; i < objects.size(); i++) { + TracksHistory tracksHistory = new TracksHistory(); + tracksHistory.setDownloadTrack(downloadTrackRepository.findOne(UUID.fromString((String) objects.get(i)[0]))); + if((String) objects.get(i)[1] != null) tracksHistory.setHistory(historyRepository.findOne(UUID.fromString((String) objects.get(i)[1]))); + + tracksHistories.add(tracksHistory); + } + } else { + return null; + } + return tracksHistories; + } } diff --git a/v3/src/main/java/ownradio/web/rest/v4/DeviceController.java b/v3/src/main/java/ownradio/web/rest/v4/DeviceController.java new file mode 100644 index 0000000..88846b0 --- /dev/null +++ b/v3/src/main/java/ownradio/web/rest/v4/DeviceController.java @@ -0,0 +1,47 @@ +package ownradio.web.rest.v4; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import ownradio.domain.Device; +import ownradio.domain.User; +import ownradio.repository.DeviceRepository; +import ownradio.service.DeviceService; +import ownradio.service.UserService; + +import java.util.UUID; + +/** + * Created by a.polunina on 03.04.2017. + */ +@Slf4j +//@CrossOrigin +@RestController +@RequestMapping("/v4/devices") +public class DeviceController { + private final DeviceRepository deviceRepository; + + @Autowired + public DeviceController(DeviceRepository deviceRepository) { + this.deviceRepository = deviceRepository; + } + + @RequestMapping(value = "/{deviceId}/registerdevice", method = RequestMethod.GET) + public ResponseEntity registerDevice(@PathVariable UUID deviceId){ + return getResponseEntityRegisterDevice(deviceId, null); + } + + @RequestMapping(value = "/{deviceId}/{deviceName}/registerdevice", method = RequestMethod.GET) + public ResponseEntity registerDevice(@PathVariable UUID deviceId, @PathVariable String deviceName){ + return getResponseEntityRegisterDevice(deviceId, deviceName); + } + + private ResponseEntity getResponseEntityRegisterDevice(UUID deviceId, String deviceName) { + if(deviceName == null) + deviceName = "New unknown device"; + deviceRepository.registerdevice(deviceId, deviceName); + return new ResponseEntity(HttpStatus.OK); + } +} diff --git a/v3/src/main/java/ownradio/web/rest/v4/HistoryController.java b/v3/src/main/java/ownradio/web/rest/v4/HistoryController.java index e592d53..1d79a63 100644 --- a/v3/src/main/java/ownradio/web/rest/v4/HistoryController.java +++ b/v3/src/main/java/ownradio/web/rest/v4/HistoryController.java @@ -78,7 +78,7 @@ public ResponseEntity save2(@PathVariable UUID deviceId, @PathVariable UUID trac private ResponseEntity getResponseEntity(@PathVariable UUID deviceId, @PathVariable UUID trackId, @RequestBody History history) { try { if(deviceService.getById(deviceId) == null || trackService.getById(trackId) == null) - return new ResponseEntity(HttpStatus.BAD_REQUEST); + return new ResponseEntity(HttpStatus.NOT_FOUND); log.info("deviceId:{} trackId: {}",deviceId.toString(),trackId.toString()); log.info("{} {} {}",history.getLastListen(), history.getIsListen(), history.getMethodid()); diff --git a/v3/src/main/java/ownradio/web/rest/v4/StatisticsController.java b/v3/src/main/java/ownradio/web/rest/v4/StatisticsController.java index ae95b5c..be604f7 100644 --- a/v3/src/main/java/ownradio/web/rest/v4/StatisticsController.java +++ b/v3/src/main/java/ownradio/web/rest/v4/StatisticsController.java @@ -7,7 +7,9 @@ import org.springframework.web.bind.annotation.*; import ownradio.domain.Device; import ownradio.domain.DownloadTrack; +import ownradio.domain.TracksHistory; import ownradio.domain.UsersRating; +import ownradio.repository.DeviceRepository; import ownradio.service.DeviceService; import ownradio.service.DownloadTrackService; import ownradio.service.UserService; @@ -73,5 +75,25 @@ public ResponseEntity getUsersRating(@PathVariable Integer countRows) { return new ResponseEntity<>(HttpStatus.NOT_FOUND); } } + + @RequestMapping(value = "/{deviceId}/{countTracks}/gettrackshistorybydevice", method = RequestMethod.GET) + public ResponseEntity getTracksHistoryByDevice(@PathVariable UUID deviceId, @PathVariable Integer countTracks) { + try { + List tracksHistories = downloadTrackService.getTracksHistoryByDevice(deviceId, countTracks); + return new ResponseEntity<>(tracksHistories, HttpStatus.OK); + }catch (Exception ex){ + return new ResponseEntity<>(HttpStatus.NOT_FOUND); + } + } + + @RequestMapping(value = "/getlastdevices", method = RequestMethod.GET) + public ResponseEntity getLastDevices() { + try{ + List lastActiveDevices = deviceService.getLastDevices(); + return new ResponseEntity<>(lastActiveDevices, HttpStatus.OK); + }catch (Exception ex){ + return new ResponseEntity<>(HttpStatus.NOT_FOUND); + } + } } diff --git a/v3/src/main/resources/data/postgresql/schema.sql b/v3/src/main/resources/data/postgresql/schema.sql index 0e79a96..af26be7 100644 --- a/v3/src/main/resources/data/postgresql/schema.sql +++ b/v3/src/main/resources/data/postgresql/schema.sql @@ -198,191 +198,6 @@ END; ' LANGUAGE plpgsql; -CREATE OR REPLACE FUNCTION getnexttrackid_v3(IN i_deviceid UUID) - RETURNS TABLE(track UUID, methodid INTEGER) AS -' -DECLARE - i_userid UUID = i_deviceid; - rnd INTEGER = (SELECT trunc(random() * 1001)); - o_methodid INTEGER; -- id метода выбора трека - owntracks INTEGER; -- количество "своих" треков пользователя (обрезаем на 900 шт) -BEGIN - -- Выбираем следующий трек - - -- Определяем количество "своих" треков пользователя, ограничивая его 900 - owntracks = (SELECT COUNT(*) - FROM ( - SELECT * - FROM ratings - WHERE userid = i_userid - AND ratingsum >= 0 - LIMIT 900) AS count); - - -- Если rnd меньше количества "своих" треков, выбираем трек из треков пользователя (добавленных им или прослушанных до конца) - -- с положительным рейтингом, за исключением прослушанных за последние сутки - - IF (rnd < owntracks) - THEN - o_methodid = 2; -- метод выбора из своих треков - RETURN QUERY - SELECT - trackid, - o_methodid - FROM ratings - WHERE userid = i_userid - AND lastlisten < localtimestamp - INTERVAL ''1 day'' - AND ratingsum >= 0 - AND (SELECT isexist - FROM tracks - WHERE recid = trackid) = 1 - ORDER BY RANDOM() - LIMIT 1; - - -- Если такой трек найден - выход из функции, возврат найденного значения - IF FOUND - THEN RETURN; - END IF; - END IF; - - -- В 1/10 случае выбираем случайный трек из ни разу не прослушанных пользователем треков - o_methodid = 3; -- метод выбора из непрослушанных треков - RETURN QUERY - SELECT - recid, - o_methodid - FROM tracks - WHERE recid NOT IN - (SELECT trackid - FROM ratings - WHERE userid = i_userid) - AND isexist = 1 - ORDER BY RANDOM() - LIMIT 1; - - -- Если такой трек найден - выход из функции, возврат найденного значения - IF FOUND - THEN RETURN; - END IF; - - -- Если предыдущие запросы вернули null, выбираем случайный трек - o_methodid = 1; -- метод выбора случайного трека - RETURN QUERY - SELECT - recid, - o_methodid - FROM tracks - WHERE isexist = 1 - ORDER BY RANDOM() - LIMIT 1; - RETURN; -END; -' -LANGUAGE plpgsql; - -CREATE OR REPLACE FUNCTION getnexttrackid_v5(IN i_deviceid UUID) - RETURNS TABLE(track UUID, methodid INTEGER) AS -' -DECLARE - i_userid UUID = i_deviceid; - rnd INTEGER = (SELECT trunc(random() * 1001)); - o_methodid INTEGER; -- id метода выбора трека - owntracks INTEGER; -- количество "своих" треков пользователя (обрезаем на 900 шт) -BEGIN - -- Выбираем следующий трек - - -- Определяем количество "своих" треков пользователя, ограничивая его 900 - owntracks = (SELECT COUNT(*) - FROM ( - SELECT * - FROM ratings - WHERE userid = i_userid - AND ratingsum >= 0 - LIMIT 900) AS count); - - -- Если rnd меньше количества "своих" треков, выбираем трек из треков пользователя (добавленных им или прослушанных до конца) - -- с положительным рейтингом, за исключением прослушанных за последние сутки - - IF (rnd < owntracks) - THEN - o_methodid = 2; -- метод выбора из своих треков - RETURN QUERY - SELECT - trackid, - o_methodid - FROM ratings - WHERE userid = i_userid - AND lastlisten < localtimestamp - INTERVAL ''1 day'' - AND ratingsum >= 0 - AND (SELECT isexist - FROM tracks - WHERE recid = trackid) = 1 - AND ((SELECT length - FROM tracks - WHERE recid = trackid) >= 120 - OR (SELECT length - FROM tracks - WHERE recid = trackid) IS NULL) - AND ((SELECT iscensorial - FROM tracks - WHERE recid = trackid) IS NULL - OR (SELECT iscensorial - FROM tracks - WHERE recid = trackid) != 0) - AND trackid NOT IN (SELECT trackid - FROM downloadtracks - WHERE reccreated > localtimestamp - INTERVAL ''1 day'') - ORDER BY RANDOM() - LIMIT 1; - - -- Если такой трек найден - выход из функции, возврат найденного значения - IF FOUND - THEN RETURN; - END IF; - END IF; - - -- В 1/10 случае выбираем случайный трек из ни разу не прослушанных пользователем треков - o_methodid = 3; -- метод выбора из непрослушанных треков - RETURN QUERY - SELECT - recid, - o_methodid - FROM tracks - WHERE recid NOT IN - (SELECT trackid - FROM ratings - WHERE userid = i_userid) - AND isexist = 1 - AND (iscensorial IS NULL OR iscensorial != 0) - AND (length > 120 OR length IS NULL) - AND recid NOT IN (SELECT trackid - FROM downloadtracks - WHERE reccreated > localtimestamp - INTERVAL ''1 day'') - ORDER BY RANDOM() - LIMIT 1; - - -- Если такой трек найден - выход из функции, возврат найденного значения - IF FOUND - THEN RETURN; - END IF; - - -- Если предыдущие запросы вернули null, выбираем случайный трек - o_methodid = 1; -- метод выбора случайного трека - RETURN QUERY - SELECT - recid, - o_methodid - FROM tracks - WHERE isexist = 1 - AND (iscensorial IS NULL OR iscensorial != 0) - AND (length > 120 OR length IS NULL) - ORDER BY RANDOM() - LIMIT 1; - RETURN; -END; -' -LANGUAGE plpgsql; - - CREATE OR REPLACE FUNCTION getnexttrack(i_deviceid UUID) RETURNS TABLE( track CHARACTER VARYING @@ -470,458 +285,74 @@ BEGIN CLOSE curs1; -- закрываем курсор -- обновляем имеющиеся коэффициенты в таблице ratios - UPDATE ratios SET ratio = temp_ratio.ratio FROM temp_ratio + UPDATE ratios SET ratio = temp_ratio.ratio, recupdated = now() FROM temp_ratio WHERE (ratios.userid1 = temp_ratio.userid1 AND ratios.userid2 = temp_ratio.userid2) OR (ratios.userid1 = temp_ratio.userid2 AND ratios.userid2 = temp_ratio.userid1); - -- если в ratios меньше пар пользователей, чем во временной таблице - вставляем недостающие записи - IF (SELECT COUNT(*) FROM ratios) < (SELECT COUNT(*) FROM temp_ratio) THEN - INSERT INTO ratios (userid1, userid2, ratio) - (SELECT tr.userid1, tr.userid2, tr.ratio FROM temp_ratio AS tr - LEFT OUTER JOIN ratios AS rr ON tr.userid1 = rr.userid1 AND tr.userid2 = rr.userid2 - WHERE rr.userid1 IS NULL OR rr.userid2 IS NULL - ); - END IF; - RETURN TRUE; -END; -' -LANGUAGE plpgsql; - - -CREATE OR REPLACE FUNCTION updateratios(i_userid uuid) - RETURNS boolean AS -' --- Функция обновляет таблицу коэффициентов схожести интересов для выбранного пользователя -DECLARE - -- объявляем курсор и запрос для него - curs1 CURSOR FOR SELECT * FROM( - -- рассчитываем матрицу коэффициентов схожести интересов для каждой пары пользователей - SELECT r.userid as userid01, r2.userid as userid02, SUM(r.ratingsum * r2.ratingsum) as s - FROM ratings r - INNER JOIN ratings r2 ON r.trackid = r2.trackid - AND r.userid != r2.userid - AND (r.userid = i_userid OR r2.userid = i_userid) - GROUP BY r.userid, r2.userid - ) AS cursor1; - cuser1 uuid; - cuser2 uuid; - cratio integer; -BEGIN - DROP TABLE IF EXISTS temp_ratio; - CREATE TEMP TABLE temp_ratio(userid1 uuid, userid2 uuid, ratio integer); - - OPEN curs1; -- открываем курсор - LOOP -- в цикле проходим по строкам результата запроса курсора - FETCH curs1 INTO cuser1, cuser2, cratio; - - IF NOT FOUND THEN EXIT; -- если данных нет - выходим - END IF; - -- если для данной пары пользователей уже записан коэффициент - пропускаем, иначе - записываем во временную таблицу - IF NOT EXISTS (SELECT * FROM temp_ratio WHERE userid1 = cuser2 AND userid2 = cuser1 OR userid1 = cuser1 AND userid2 = cuser2) THEN - INSERT INTO temp_ratio(userid1, userid2, ratio) - VALUES (cuser1, cuser2, cratio); - END IF; - END LOOP; - CLOSE curs1; -- закрываем курсор - - -- обновляем имеющиеся коэффициенты в таблице ratios - UPDATE ratios SET ratio = temp_ratio.ratio FROM temp_ratio - WHERE (ratios.userid1 = temp_ratio.userid1 AND ratios.userid2 = temp_ratio.userid2) - OR (ratios.userid1 = temp_ratio.userid2 AND ratios.userid2 = temp_ratio.userid1); - - -- если в ratios меньше пар пользователей, чем во временной таблице - вставляем недостающие записи - IF (SELECT COUNT(*) FROM ratios WHERE userid1 = i_userid or userid2 = i_userid) < (SELECT COUNT(*) FROM temp_ratio) THEN - INSERT INTO ratios (userid1, userid2, ratio) - (SELECT tr.userid1, tr.userid2, tr.ratio FROM temp_ratio AS tr - LEFT OUTER JOIN ratios AS rr ON tr.userid1 = rr.userid1 AND tr.userid2 = rr.userid2 - WHERE rr.userid1 IS NULL OR rr.userid2 IS NULL - ); - END IF; -RETURN TRUE; -END; -' -LANGUAGE plpgsql; - - -CREATE OR REPLACE FUNCTION getnexttrackid_v6(IN i_deviceid uuid) - RETURNS TABLE(track uuid, methodid integer) AS -' -DECLARE - i_userid UUID = i_deviceid; - rnd INTEGER = (SELECT trunc(random() * 1001)); - o_methodid INTEGER; -- id метода выбора трека - owntracks INTEGER; -- количество "своих" треков пользователя (обрезаем на 900 шт) - arrusers uuid ARRAY; -- массив пользователей для i_userid с неотрицательнымм коэффициентами схожести интересов -BEGIN - -- Выбираем следующий трек - - -- Определяем количество "своих" треков пользователя, ограничивая его 900 - owntracks = (SELECT COUNT(*) - FROM ( - SELECT * - FROM ratings - WHERE userid = i_userid - AND ratingsum >= 0 - LIMIT 900) AS count); - - -- Если rnd меньше количества "своих" треков, выбираем трек из треков пользователя (добавленных им или прослушанных до конца) - -- с положительным рейтингом, за исключением прослушанных за последние сутки - - IF (rnd < owntracks) - THEN - o_methodid = 2; -- метод выбора из своих треков - RETURN QUERY - SELECT - trackid, - o_methodid - FROM ratings - WHERE userid = i_userid - AND lastlisten < localtimestamp - INTERVAL ''1 day'' - AND ratingsum >= 0 - AND (SELECT isexist - FROM tracks - WHERE recid = trackid) = 1 - AND ((SELECT length - FROM tracks - WHERE recid = trackid) >= 120 - OR (SELECT length - FROM tracks - WHERE recid = trackid) IS NULL) - AND ((SELECT iscensorial - FROM tracks - WHERE recid = trackid) IS NULL - OR (SELECT iscensorial - FROM tracks - WHERE recid = trackid) != 0) - AND trackid NOT IN (SELECT trackid - FROM downloadtracks - WHERE reccreated > localtimestamp - INTERVAL ''1 day'') - ORDER BY RANDOM() - LIMIT 1; - - -- Если такой трек найден - выход из функции, возврат найденного значения - IF FOUND - THEN RETURN; - END IF; - END IF; - - -- Если rnd больше количества "своих" треков - рекомендуем трек из треков пользователя с наибольшим - -- коэффициентом схожести интересов и наибольшим рейтингом прослушивания - - -- Выберем всех пользователей с неотрицательным коэффициентом схожести интересов для i_userid - -- отсортировав по убыванию коэффициентов - arrusers = (SELECT ARRAY (SELECT CASE WHEN userid1 = i_userid THEN userid2 - WHEN userid2 = i_userid THEN userid1 - ELSE NULL - END - FROM ratios - WHERE userid1 = i_userid OR userid2 = i_userid - AND ratio >= 0 - ORDER BY ratio DESC - )); - -- Выбираем пользователя i, с которым у него максимальный коэффициент. Среди его треков ищем трек - -- с максимальным рейтингом прослушивания, за исключением уже прослушанных пользователем i_userid. - -- Если рекомендовать нечего - берем следующего пользователя с наибольшим коэффициентом из оставшихся. - FOR i IN 1.. (SELECT COUNT (*) FROM unnest(arrusers)) LOOP - o_methodid = 4; -- метод выбора из рекомендованных треков - RETURN QUERY - SELECT - trackid, - o_methodid - FROM ratings - WHERE userid = arrusers[i] - AND ratingsum > 0 - AND trackid NOT IN (SELECT trackid FROM ratings WHERE userid = i_userid) - AND trackid NOT IN (SELECT trackid - FROM downloadtracks - WHERE deviceid = i_userid - AND reccreated > localtimestamp - INTERVAL ''1 day'') - AND (SELECT isexist - FROM tracks - WHERE recid = trackid) = 1 - AND ((SELECT length - FROM tracks - WHERE recid = trackid) >= 120 - OR (SELECT length - FROM tracks - WHERE recid = trackid) IS NULL) - AND ((SELECT iscensorial - FROM tracks - WHERE recid = trackid) IS NULL - OR (SELECT iscensorial - FROM tracks - WHERE recid = trackid) != 0) - ORDER BY ratingsum DESC - LIMIT 1; - -- Если нашли что рекомендовать - выходим из функции - IF found THEN - RETURN; - END IF; - END LOOP; - - -- При отсутствии рекомендаций, выдавать случайный трек из непрослушанных треков с неотрицательным - -- рейтингом среди пользователей со схожим вкусом. - FOR i IN 1.. (SELECT COUNT (*) FROM unnest(arrusers)) LOOP - o_methodid = 5; -- метод выбора из непрослушанных треков с неотрицательным рейтингом среди пользователей со схожим вкусом - RETURN QUERY - SELECT - recid, - o_methodid - FROM tracks - WHERE recid NOT IN (SELECT trackid FROM ratings WHERE userid = arrusers[i] AND ratingsum < 0) - AND recid NOT IN (SELECT trackid FROM ratings WHERE userid = i_userid) - AND isexist = 1 - AND (iscensorial IS NULL OR iscensorial != 0) - AND (length > 120 OR length IS NULL) - AND recid NOT IN (SELECT trackid - FROM downloadtracks - WHERE reccreated > localtimestamp - INTERVAL ''1 day'') - ORDER BY RANDOM() - LIMIT 1; - -- Если нашли что рекомендовать - выходим из функции - IF found THEN - RETURN; - END IF; - END LOOP; - - -- Если таких треков нет - выбираем случайный трек из ни разу не прослушанных пользователем треков - o_methodid = 3; -- метод выбора из непрослушанных треков - RETURN QUERY - SELECT - recid, - o_methodid - FROM tracks - WHERE recid NOT IN - (SELECT trackid - FROM ratings - WHERE userid = i_userid) - AND isexist = 1 - AND (iscensorial IS NULL OR iscensorial != 0) - AND (length > 120 OR length IS NULL) - AND recid NOT IN (SELECT trackid - FROM downloadtracks - WHERE reccreated > localtimestamp - INTERVAL ''1 day'') - ORDER BY RANDOM() - LIMIT 1; - - -- Если такой трек найден - выход из функции, возврат найденного значения - IF FOUND - THEN RETURN; - END IF; - - -- Если предыдущие запросы вернули null, выбираем случайный трек - o_methodid = 1; -- метод выбора случайного трека - RETURN QUERY - SELECT - recid, - o_methodid - FROM tracks - WHERE isexist = 1 - AND (iscensorial IS NULL OR iscensorial != 0) - AND (length > 120 OR length IS NULL) - ORDER BY RANDOM() - LIMIT 1; - RETURN; + -- если в ratios меньше пар пользователей, чем во временной таблице - вставляем недостающие записи + IF (SELECT COUNT(*) FROM ratios) < (SELECT COUNT(*) FROM temp_ratio) THEN + INSERT INTO ratios (userid1, userid2, ratio, reccreated) + (SELECT tr.userid1, tr.userid2, tr.ratio, now() FROM temp_ratio AS tr + LEFT OUTER JOIN ratios AS rr ON tr.userid1 = rr.userid1 AND tr.userid2 = rr.userid2 OR tr.userid1 = rr.userid2 AND tr.userid2 = rr.userid1 + WHERE rr.userid1 IS NULL OR rr.userid2 IS NULL + ); + END IF; + RETURN TRUE; END; ' LANGUAGE plpgsql; -CREATE OR REPLACE FUNCTION getnexttrackid_v7(IN i_deviceid uuid) - RETURNS TABLE(track uuid, methodid integer) AS -' --- Функция выдачи следующего трека пользователю --- С учетом рекомендаций от других пользователей +CREATE OR REPLACE FUNCTION updateratios(i_userid uuid) + RETURNS boolean AS +' +-- Функция обновляет таблицу коэффициентов схожести интересов для выбранного пользователя DECLARE - i_userid UUID = i_deviceid; - rnd INTEGER = (SELECT trunc(random() * 1001)); - o_methodid INTEGER; -- id метода выбора трека - owntracks INTEGER; -- количество "своих" треков пользователя (обрезаем на 900 шт) - arrusers uuid ARRAY; -- массив пользователей для i_userid с неотрицательнымм коэффициентами схожести интересов - exceptusers uuid ARRAY; -- массив пользователей для i_userid с котороми не было пересечений по трекам + -- объявляем курсор и запрос для него + curs1 CURSOR FOR SELECT * FROM( + -- рассчитываем матрицу коэффициентов схожести интересов для каждой пары пользователей + SELECT r.userid as userid01, r2.userid as userid02, SUM(r.ratingsum * r2.ratingsum) as s + FROM ratings r + INNER JOIN ratings r2 ON r.trackid = r2.trackid + AND r.userid != r2.userid + AND (r.userid = i_userid OR r2.userid = i_userid) + GROUP BY r.userid, r2.userid + ) AS cursor1; + cuser1 uuid; + cuser2 uuid; + cratio integer; BEGIN - -- Выбираем следующий трек - - -- Определяем количество "своих" треков пользователя, ограничивая его 900 - owntracks = (SELECT COUNT(*) - FROM ( - SELECT * - FROM ratings - WHERE userid = i_userid - AND ratingsum >= 0 - LIMIT 900) AS count); - - -- Если rnd меньше количества "своих" треков, выбираем трек из треков пользователя (добавленных им или прослушанных до конца) - -- с положительным рейтингом, за исключением прослушанных за последние сутки - - IF (rnd < owntracks) - THEN - o_methodid = 2; -- метод выбора из своих треков - RETURN QUERY - SELECT - trackid, - o_methodid - FROM ratings - WHERE userid = i_userid - AND lastlisten < localtimestamp - INTERVAL ''1 day'' - AND ratingsum >= 0 - AND (SELECT isexist - FROM tracks - WHERE recid = trackid) = 1 - AND ((SELECT length - FROM tracks - WHERE recid = trackid) >= 120 - OR (SELECT length - FROM tracks - WHERE recid = trackid) IS NULL) - AND ((SELECT iscensorial - FROM tracks - WHERE recid = trackid) IS NULL - OR (SELECT iscensorial - FROM tracks - WHERE recid = trackid) != 0) - AND trackid NOT IN (SELECT trackid - FROM downloadtracks - WHERE reccreated > localtimestamp - INTERVAL ''1 day'') - ORDER BY RANDOM() - LIMIT 1; - - -- Если такой трек найден - выход из функции, возврат найденного значения - IF FOUND - THEN RETURN; - END IF; - END IF; + DROP TABLE IF EXISTS temp_ratio; + CREATE TEMP TABLE temp_ratio(userid1 uuid, userid2 uuid, ratio integer); - -- Если rnd больше количества "своих" треков - рекомендуем трек из треков пользователя с наибольшим - -- коэффициентом схожести интересов и наибольшим рейтингом прослушивания + OPEN curs1; -- открываем курсор + LOOP -- в цикле проходим по строкам результата запроса курсора + FETCH curs1 INTO cuser1, cuser2, cratio; - -- Выберем всех пользователей с неотрицательным коэффициентом схожести интересов для i_userid - -- отсортировав по убыванию коэффициентов - arrusers = (SELECT ARRAY (SELECT CASE WHEN userid1 = i_userid THEN userid2 - WHEN userid2 = i_userid THEN userid1 - ELSE NULL - END - FROM ratios - WHERE userid1 = (i_userid OR userid2 = i_userid) AND ratio >= 0 - ORDER BY ratio DESC - )); - -- Выбираем пользователя i, с которым у него максимальный коэффициент. Среди его треков ищем трек - -- с максимальным рейтингом прослушивания, за исключением уже прослушанных пользователем i_userid. - -- Если рекомендовать нечего - берем следующего пользователя с наибольшим коэффициентом из оставшихся. - FOR i IN 1.. (SELECT COUNT (*) FROM unnest(arrusers)) LOOP - o_methodid = 4; -- метод выбора из рекомендованных треков - RETURN QUERY - SELECT - trackid, - o_methodid - FROM ratings - WHERE userid = arrusers[i] - AND ratingsum > 0 - AND trackid NOT IN (SELECT trackid FROM ratings WHERE userid = i_userid) - AND trackid NOT IN (SELECT trackid - FROM downloadtracks - WHERE deviceid = i_deviceid - AND reccreated > localtimestamp - INTERVAL ''1 day'') - AND (SELECT isexist - FROM tracks - WHERE recid = trackid) = 1 - AND ((SELECT length - FROM tracks - WHERE recid = trackid) >= 120 - OR (SELECT length - FROM tracks - WHERE recid = trackid) IS NULL) - AND ((SELECT iscensorial - FROM tracks - WHERE recid = trackid) IS NULL - OR (SELECT iscensorial - FROM tracks - WHERE recid = trackid) != 0) - ORDER BY ratingsum DESC, RANDOM() - LIMIT 1; - -- Если нашли что рекомендовать - выходим из функции - IF found THEN - RETURN; + IF NOT FOUND THEN EXIT; -- если данных нет - выходим END IF; - END LOOP; - -- При отсутствии рекомендаций, выдавать случайный трек из непрослушанных треков с неотрицательным - -- рейтингом среди пользователей с которыми не было пересечений по трекам. - exceptusers = (SELECT ARRAY ( - SELECT * FROM ( - SELECT recid FROM users WHERE recid != i_userid - EXCEPT - (SELECT CASE WHEN userid1 = i_userid THEN userid2 - WHEN userid2 = i_userid THEN userid1 - ELSE NULL - END - FROM ratios WHERE userid1 = i_userid OR userid2 = i_userid) - ) AS us - ORDER BY RANDOM() - ) - ); - FOR i IN 1.. (SELECT COUNT (*) FROM unnest(exceptusers)) LOOP - o_methodid = 6; -- метод выбора из непрослушанных треков с неотрицательным рейтингом среди пользователей с которыми не было пересечений - RETURN QUERY - SELECT - recid, - o_methodid - FROM tracks - WHERE recid IN (SELECT trackid FROM ratings WHERE userid = exceptusers[i] AND ratingsum >= 0) - AND recid NOT IN (SELECT trackid FROM ratings WHERE userid = i_userid) - AND isexist = 1 - AND (iscensorial IS NULL OR iscensorial != 0) - AND (length > 120 OR length IS NULL) - AND recid NOT IN (SELECT trackid - FROM downloadtracks - WHERE reccreated > localtimestamp - INTERVAL ''1 day'') - ORDER BY RANDOM() - LIMIT 1; - -- Если нашли что рекомендовать - выходим из функции - IF found THEN - RETURN; - ELSE - + -- если для данной пары пользователей уже записан коэффициент - пропускаем, иначе - записываем во временную таблицу + IF NOT EXISTS (SELECT * FROM temp_ratio WHERE userid1 = cuser2 AND userid2 = cuser1 OR userid1 = cuser1 AND userid2 = cuser2) THEN + INSERT INTO temp_ratio(userid1, userid2, ratio) + VALUES (cuser1, cuser2, cratio); END IF; END LOOP; + CLOSE curs1; -- закрываем курсор - -- Если таких треков нет - выбираем случайный трек из ни разу не прослушанных пользователем треков - o_methodid = 3; -- метод выбора из непрослушанных треков - RETURN QUERY - SELECT - recid, - o_methodid - FROM tracks - WHERE recid NOT IN - (SELECT trackid - FROM ratings - WHERE userid = i_userid) - AND isexist = 1 - AND (iscensorial IS NULL OR iscensorial != 0) - AND (length > 120 OR length IS NULL) - AND recid NOT IN (SELECT trackid - FROM downloadtracks - WHERE reccreated > localtimestamp - INTERVAL ''1 day'') - ORDER BY RANDOM() - LIMIT 1; + -- обновляем имеющиеся коэффициенты в таблице ratios + UPDATE ratios SET ratio = temp_ratio.ratio, recupdated = now() FROM temp_ratio + WHERE (ratios.userid1 = temp_ratio.userid1 AND ratios.userid2 = temp_ratio.userid2) + OR (ratios.userid1 = temp_ratio.userid2 AND ratios.userid2 = temp_ratio.userid1); - -- Если такой трек найден - выход из функции, возврат найденного значения - IF FOUND - THEN RETURN; + -- если в ratios меньше пар пользователей, чем во временной таблице - вставляем недостающие записи + IF (SELECT COUNT(*) FROM ratios WHERE userid1 = i_userid or userid2 = i_userid) < (SELECT COUNT(*) FROM temp_ratio) THEN + INSERT INTO ratios (userid1, userid2, ratio, reccreated) + (SELECT tr.userid1, tr.userid2, tr.ratio, now() FROM temp_ratio AS tr + LEFT OUTER JOIN ratios AS rr ON tr.userid1 = rr.userid1 AND tr.userid2 = rr.userid2 OR tr.userid1 = rr.userid2 AND tr.userid2 = rr.userid1 + WHERE rr.userid1 IS NULL OR rr.userid2 IS NULL + ); END IF; - - -- Если предыдущие запросы вернули null, выбираем случайный трек - o_methodid = 1; -- метод выбора случайного трека - RETURN QUERY - SELECT - recid, - o_methodid - FROM tracks - WHERE isexist = 1 - AND (iscensorial IS NULL OR iscensorial != 0) - AND (length > 120 OR length IS NULL) - ORDER BY RANDOM() - LIMIT 1; - RETURN; +RETURN TRUE; END; ' LANGUAGE plpgsql; @@ -1188,7 +619,7 @@ CREATE OR REPLACE FUNCTION getuserdevices(IN i_userid uuid) ' BEGIN RETURN QUERY - SELECT * FROM devices WHERE userid = i_userid; + SELECT * FROM devices WHERE devices.userid = i_userid; END; ' LANGUAGE plpgsql; @@ -1414,4 +845,79 @@ BEGIN RETURN; END; ' +LANGUAGE plpgsql; + + + +CREATE OR REPLACE FUNCTION registerdevice( + i_deviceid uuid, + i_devicename character varying) + RETURNS boolean AS +' +BEGIN + -- Функция регистрации нового устройства + + -- Добавляем устройство, если его еще не существует + -- Если ID устройства еще нет в БД + IF NOT EXISTS(SELECT recid + FROM devices + WHERE recid = i_deviceid) + THEN + + -- Добавляем нового пользователя + INSERT INTO users (recid, recname, reccreated) SELECT + i_deviceid, + i_devicename, + now() + WHERE NOT EXISTS(SELECT recid FROM users WHERE recid = i_deviceid); + + -- Добавляем новое устройство + INSERT INTO devices (recid, userid, recname, reccreated) SELECT + i_deviceid, + i_deviceid, + i_devicename, + now(); + END IF; + RETURN TRUE; +END; +' +LANGUAGE plpgsql; + + +CREATE OR REPLACE FUNCTION getlastdevices() + RETURNS TABLE(recid character varying) AS +' +BEGIN + + RETURN QUERY SELECT CAST((dev.recid) AS CHARACTER VARYING) + FROM devices dev + INNER JOIN downloadtracks down + ON dev.recid = down.deviceid + GROUP BY dev.recid + ORDER BY MAX(down.reccreated) DESC + LIMIT 100; + +END; +' +LANGUAGE plpgsql; + + +CREATE OR REPLACE FUNCTION gettrackshistorybydevice( + IN i_deviceid uuid, + IN i_count integer) + RETURNS TABLE(downloadtrackrecid character varying, historyrecid character varying) AS +' +BEGIN + IF i_count < 0 THEN + i_count = null; + END IF; + RETURN QUERY SELECT CAST((d.recid) AS CHARACTER VARYING), CAST((h.recid) AS CHARACTER VARYING) + FROM downloadtracks d + LEFT OUTER JOIN histories h + ON h.deviceid = d.deviceid AND h.trackid = d.trackid + WHERE d.deviceid = i_deviceid + ORDER BY d.reccreated DESC, h.reccreated DESC, h.lastlisten DESC + LIMIT i_count; +END; +' LANGUAGE plpgsql; \ No newline at end of file From 2f9d07e1c7cadd8f1bfab9e724c261b8585ed30a Mon Sep 17 00:00:00 2001 From: Igor Limanskij Date: Tue, 11 Apr 2017 12:16:53 +0300 Subject: [PATCH 33/44] Update ownradio_db_v3.sql --- dbScripts/ownradio_db_v3.sql | 52 ++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/dbScripts/ownradio_db_v3.sql b/dbScripts/ownradio_db_v3.sql index bf31ac5..4c45e0f 100644 --- a/dbScripts/ownradio_db_v3.sql +++ b/dbScripts/ownradio_db_v3.sql @@ -1267,3 +1267,55 @@ $BODY$ ROWS 1000; ALTER FUNCTION getnexttrackid_v7(uuid) OWNER TO "postgres"; + + +-- Function: public.getrecommendedtrackid_v1(uuid) + +-- DROP FUNCTION public.getrecommendedtrackid_v1(uuid); + +CREATE OR REPLACE FUNCTION public.getrecommendedtrackid_v1(userid uuid) + RETURNS uuid AS +$BODY$ + +DECLARE +preferenced_track uuid; + +BEGIN + -- Соединяем таблицу tracks с таблицой сумм произведений рейтинга трека на коэффициент + -- у конкретного пользователя для возможности вывода дополнительной информации о треке + -- в отладочных целях + SELECT tracks.recid INTO preferenced_track + --tracks.recid, table2.sum_rate, tracks.localdevicepathupload, tracks.path + FROM tracks + INNER JOIN ( + -- Группируем по треку и считаем сумму произведений рейтингов на коэффициент для + -- каждого из них + SELECT trackid, SUM(track_rating) AS sum_rate + FROM( + -- Запрашиваем таблицу с рейтингом всех треков, оцененных пользователями, которые имеют коэффициент + -- с исходным, умноженным на их коэффициент + SELECT ratings.trackid, ratings.ratingsum * ratios.ratio AS track_rating, ratings.userid, ratios.ratio + FROM ratings, ratios + -- Будем считать рейтинги треков, только у пользователей с положительным коэффициентом с исходным + WHERE ratios.ratio > 0 AND (ratings.userid = ratios.userid2 AND ratios.userid1 = $1 OR ratings.userid = ratios.userid1 AND ratios.userid2 = $1) + ) AS TracksRatings + GROUP BY trackid + ORDER BY sum_rate DESC + ) AS table2 + ON tracks.recid = table2.trackid + AND tracks.isexist = 1 + AND tracks.iscensorial <> 0 + AND tracks.length >= 120 + AND tracks.recid NOT IN (SELECT trackid FROM downloadtracks + WHERE reccreated > localtimestamp - INTERVAL '1 day') + -- В итоге рекомендоваться будут только треки с положительной суммой произведений рейтингов на коэффициенты + AND sum_rate > 0 + ORDER BY table2.sum_rate DESC + LIMIT 1; + RETURN preferenced_track; +END; +$BODY$ + LANGUAGE plpgsql VOLATILE + COST 100; +ALTER FUNCTION public.getrecommendedtrackid_v1(uuid) + OWNER TO "i.limanskij"; From df093bcb49c67dabb5c14547144f46f237bfd08e Mon Sep 17 00:00:00 2001 From: Igor Limanskij Date: Tue, 11 Apr 2017 12:18:05 +0300 Subject: [PATCH 34/44] Update ownradio_db_v3.sql --- dbScripts/ownradio_db_v3.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dbScripts/ownradio_db_v3.sql b/dbScripts/ownradio_db_v3.sql index 4c45e0f..b109822 100644 --- a/dbScripts/ownradio_db_v3.sql +++ b/dbScripts/ownradio_db_v3.sql @@ -1318,4 +1318,4 @@ $BODY$ LANGUAGE plpgsql VOLATILE COST 100; ALTER FUNCTION public.getrecommendedtrackid_v1(uuid) - OWNER TO "i.limanskij"; + OWNER TO postgres; From 9e95298ddcce7e94109e6423a00d8595601afca4 Mon Sep 17 00:00:00 2001 From: Alexandra Polunina Date: Tue, 11 Apr 2017 13:06:13 +0300 Subject: [PATCH 35/44] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8?= =?UTF-8?q?=D0=BB=D0=B0=20=D0=B2=D1=8B=D0=B2=D0=BE=D0=B4=20=D0=BF=D1=83?= =?UTF-8?q?=D1=82=D0=B8=20=D0=B7=D0=B0=D0=B3=D1=80=D1=83=D0=B7=D0=BA=D0=B8?= =?UTF-8?q?=20=D1=82=D1=80=D0=B5=D0=BA=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- v3/src/main/java/ownradio/web/rest/v4/TrackController.java | 1 + 1 file changed, 1 insertion(+) diff --git a/v3/src/main/java/ownradio/web/rest/v4/TrackController.java b/v3/src/main/java/ownradio/web/rest/v4/TrackController.java index 98993d8..a66790d 100644 --- a/v3/src/main/java/ownradio/web/rest/v4/TrackController.java +++ b/v3/src/main/java/ownradio/web/rest/v4/TrackController.java @@ -148,6 +148,7 @@ public ResponseEntity getNextTrack(@PathVariable UUID deviceId) { trackInfo.put("artist", track.getArtist()); else trackInfo.put("artist", "Unknown artist"); + trackInfo.put("pathupload", track.getLocaldevicepathupload()); From 5740b0dc053436ebe9d5a973bf1e043916468b1d Mon Sep 17 00:00:00 2001 From: Igor Limanskij Date: Wed, 12 Apr 2017 11:00:55 +0300 Subject: [PATCH 36/44] Update ownradio_db_v3.sql --- dbScripts/ownradio_db_v3.sql | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/dbScripts/ownradio_db_v3.sql b/dbScripts/ownradio_db_v3.sql index b109822..fec2755 100644 --- a/dbScripts/ownradio_db_v3.sql +++ b/dbScripts/ownradio_db_v3.sql @@ -1295,7 +1295,9 @@ BEGIN -- Запрашиваем таблицу с рейтингом всех треков, оцененных пользователями, которые имеют коэффициент -- с исходным, умноженным на их коэффициент SELECT ratings.trackid, ratings.ratingsum * ratios.ratio AS track_rating, ratings.userid, ratios.ratio - FROM ratings, ratios + FROM --ratings, + (SELECT ratings.trackid, ratings.ratingsum, ratings.userid FROM ratings WHERE ratings.userid <> $1) AS ratings_table, + ratios -- Будем считать рейтинги треков, только у пользователей с положительным коэффициентом с исходным WHERE ratios.ratio > 0 AND (ratings.userid = ratios.userid2 AND ratios.userid1 = $1 OR ratings.userid = ratios.userid1 AND ratios.userid2 = $1) ) AS TracksRatings From ba73ab0ca3d3a1bd1e0ae8d9c60c18099da4e993 Mon Sep 17 00:00:00 2001 From: Igor Limanskij Date: Wed, 12 Apr 2017 11:54:23 +0300 Subject: [PATCH 37/44] Update ownradio_db_v3.sql --- dbScripts/ownradio_db_v3.sql | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/dbScripts/ownradio_db_v3.sql b/dbScripts/ownradio_db_v3.sql index fec2755..9181ab3 100644 --- a/dbScripts/ownradio_db_v3.sql +++ b/dbScripts/ownradio_db_v3.sql @@ -1273,7 +1273,7 @@ ALTER FUNCTION getnexttrackid_v7(uuid) -- DROP FUNCTION public.getrecommendedtrackid_v1(uuid); -CREATE OR REPLACE FUNCTION public.getrecommendedtrackid_v1(userid uuid) +CREATE OR REPLACE FUNCTION public.getrecommendedtrackid_v1(in_userid uuid) RETURNS uuid AS $BODY$ @@ -1294,20 +1294,30 @@ BEGIN FROM( -- Запрашиваем таблицу с рейтингом всех треков, оцененных пользователями, которые имеют коэффициент -- с исходным, умноженным на их коэффициент - SELECT ratings.trackid, ratings.ratingsum * ratios.ratio AS track_rating, ratings.userid, ratios.ratio + SELECT ratings_table.trackid, ratings_table.ratingsum * ratios.ratio AS track_rating, ratings_table.userid, ratios.ratio FROM --ratings, - (SELECT ratings.trackid, ratings.ratingsum, ratings.userid FROM ratings WHERE ratings.userid <> $1) AS ratings_table, + -- Выбирем все оценки треков, кроме оценок, данных исходным пользователем + (SELECT ratings.trackid, ratings.ratingsum, ratings.userid FROM ratings WHERE ratings.userid <> in_userid) AS ratings_table, ratios - -- Будем считать рейтинги треков, только у пользователей с положительным коэффициентом с исходным - WHERE ratios.ratio > 0 AND (ratings.userid = ratios.userid2 AND ratios.userid1 = $1 OR ratings.userid = ratios.userid1 AND ratios.userid2 = $1) + -- Считать рейтинги треков, только у пользователей с положительным коэффициентом совпадения вкусов с исходным + WHERE ratios.ratio > 0 + -- Выбираем рейтинги треков у тех пользователей, у которых есть пересечение + -- с исходным в таблице ratios (кэффициенты совпадения вкусов), проверяя сначала + -- с левой стороны + AND ((ratings_table.userid = ratios.userid2 AND ratios.userid1 = in_userid) + -- потом с правой + OR (ratings_table.userid = ratios.userid1 AND ratios.userid2 = in_userid)) ) AS TracksRatings GROUP BY trackid ORDER BY sum_rate DESC ) AS table2 ON tracks.recid = table2.trackid + -- Трек должен существовать на сервере AND tracks.isexist = 1 + -- Трек не должен быть помечен как нецензурный AND tracks.iscensorial <> 0 AND tracks.length >= 120 + -- Трек не должен был выдаваться в течении последних суток AND tracks.recid NOT IN (SELECT trackid FROM downloadtracks WHERE reccreated > localtimestamp - INTERVAL '1 day') -- В итоге рекомендоваться будут только треки с положительной суммой произведений рейтингов на коэффициенты From 72d12b62eb223e38f0615c47f8bce4030be4a9c4 Mon Sep 17 00:00:00 2001 From: Igor Limanskij Date: Wed, 12 Apr 2017 14:33:24 +0300 Subject: [PATCH 38/44] =?UTF-8?q?=D0=9B=D0=B8=D0=BC=D0=B8=D1=82=20=D0=BD?= =?UTF-8?q?=D0=B0=20=D0=B8=D0=BD=D1=82=D0=B5=D1=80=D0=B2=D0=B0=D0=BB=20?= =?UTF-8?q?=D0=B2=D1=80=D0=B5=D0=BC=D0=B5=D0=BD=D0=B8=20=D1=81=20=D0=BF?= =?UTF-8?q?=D0=BE=D1=81=D0=BB=D0=B5=D0=B4=D0=BD=D0=B5=D0=B9=20=D0=B7=D0=B0?= =?UTF-8?q?=D0=B3=D1=80=D1=83=D0=B7=D0=BA=D0=B8=20=D1=82=D1=80=D0=B5=D0=BA?= =?UTF-8?q?=D0=B0=20=D1=83=D0=B2=D0=B5=D0=BB=D0=B8=D1=87=D0=B5=D0=BD=20?= =?UTF-8?q?=D1=81=201=20=D0=B4=D0=BD=D1=8F=20=D0=BD=D0=B0=202=20=D0=BC?= =?UTF-8?q?=D0=B5=D1=81=D1=8F=D1=86=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dbScripts/ownradio_db_v3.sql | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/dbScripts/ownradio_db_v3.sql b/dbScripts/ownradio_db_v3.sql index 9181ab3..480156c 100644 --- a/dbScripts/ownradio_db_v3.sql +++ b/dbScripts/ownradio_db_v3.sql @@ -1312,16 +1312,13 @@ BEGIN ORDER BY sum_rate DESC ) AS table2 ON tracks.recid = table2.trackid - -- Трек должен существовать на сервере - AND tracks.isexist = 1 - -- Трек не должен быть помечен как нецензурный - AND tracks.iscensorial <> 0 + AND tracks.isexist = 1 -- Трек должен существовать на сервере + AND tracks.iscensorial <> 0 -- Трек не должен быть помечен как нецензурный AND tracks.length >= 120 - -- Трек не должен был выдаваться в течении последних суток + -- Трек не должен был выдаваться в течении последних двух месяцев AND tracks.recid NOT IN (SELECT trackid FROM downloadtracks - WHERE reccreated > localtimestamp - INTERVAL '1 day') - -- В итоге рекомендоваться будут только треки с положительной суммой произведений рейтингов на коэффициенты - AND sum_rate > 0 + WHERE reccreated > localtimestamp - INTERVAL '2 months') + AND sum_rate > 0 -- В итоге рекомендоваться будут только треки с положительной суммой произведений рейтингов на коэффициенты ORDER BY table2.sum_rate DESC LIMIT 1; RETURN preferenced_track; From ac4c1cf6dc44955ccc8640de5b0b25f821a418b8 Mon Sep 17 00:00:00 2001 From: Igor Limanskij Date: Thu, 13 Apr 2017 09:26:33 +0300 Subject: [PATCH 39/44] Update ownradio_db_v3.sql --- dbScripts/ownradio_db_v3.sql | 40 +++++++++++++++++------------------- 1 file changed, 19 insertions(+), 21 deletions(-) diff --git a/dbScripts/ownradio_db_v3.sql b/dbScripts/ownradio_db_v3.sql index 480156c..9d33f24 100644 --- a/dbScripts/ownradio_db_v3.sql +++ b/dbScripts/ownradio_db_v3.sql @@ -1283,42 +1283,40 @@ preferenced_track uuid; BEGIN -- Соединяем таблицу tracks с таблицой сумм произведений рейтинга трека на коэффициент -- у конкретного пользователя для возможности вывода дополнительной информации о треке - -- в отладочных целях + -- в отладочных целях и для фильтра по столбцам tracks SELECT tracks.recid INTO preferenced_track --tracks.recid, table2.sum_rate, tracks.localdevicepathupload, tracks.path FROM tracks INNER JOIN ( - -- Группируем по треку и считаем сумму произведений рейтингов на коэффициент для - -- каждого из них + --Группируем по треку и считаем сумму произведений рейтингов на коэффициент для + --каждого из них SELECT trackid, SUM(track_rating) AS sum_rate FROM( - -- Запрашиваем таблицу с рейтингом всех треков, оцененных пользователями, которые имеют коэффициент - -- с исходным, умноженным на их коэффициент - SELECT ratings_table.trackid, ratings_table.ratingsum * ratios.ratio AS track_rating, ratings_table.userid, ratios.ratio - FROM --ratings, - -- Выбирем все оценки треков, кроме оценок, данных исходным пользователем - (SELECT ratings.trackid, ratings.ratingsum, ratings.userid FROM ratings WHERE ratings.userid <> in_userid) AS ratings_table, - ratios - -- Считать рейтинги треков, только у пользователей с положительным коэффициентом совпадения вкусов с исходным - WHERE ratios.ratio > 0 - -- Выбираем рейтинги треков у тех пользователей, у которых есть пересечение - -- с исходным в таблице ratios (кэффициенты совпадения вкусов), проверяя сначала - -- с левой стороны - AND ((ratings_table.userid = ratios.userid2 AND ratios.userid1 = in_userid) + --Запрашиваем таблицу с рейтингом всех треков, оцененных пользователями, которые имеют коэффициент + --с исходным, умноженным на их коэффициент + SELECT ratings.trackid, ratings.ratingsum * ratios.ratio AS track_rating, ratings.userid, ratios.ratio + FROM ratings + INNER JOIN ratios + --Выбираем рейтинги треков у тех пользователей, у которых есть пересечение + --с исходным в таблице ratios (кэффициенты совпадения вкусов), проверяя сначала + --с левой стороны + ON ((ratings.userid = ratios.userid2 AND ratios.userid1 = in_userid) -- потом с правой - OR (ratings_table.userid = ratios.userid1 AND ratios.userid2 = in_userid)) + OR (ratings.userid = ratios.userid1 AND ratios.userid2 = in_userid)) + AND ratings.userid <> in_userid --Выбирем все оценки треков, кроме оценок, данных исходным пользователем + AND ratios.ratio > 0 --Считать рейтинги треков, только у пользователей с положительным коэффициентом совпадения вкусов с исходным ) AS TracksRatings GROUP BY trackid ORDER BY sum_rate DESC ) AS table2 ON tracks.recid = table2.trackid - AND tracks.isexist = 1 -- Трек должен существовать на сервере - AND tracks.iscensorial <> 0 -- Трек не должен быть помечен как нецензурный + AND tracks.isexist = 1 --Трек должен существовать на сервере + AND tracks.iscensorial <> 0 --Трек не должен быть помечен как нецензурный AND tracks.length >= 120 - -- Трек не должен был выдаваться в течении последних двух месяцев + --Трек не должен был выдаваться в течении последних двух месяцев AND tracks.recid NOT IN (SELECT trackid FROM downloadtracks WHERE reccreated > localtimestamp - INTERVAL '2 months') - AND sum_rate > 0 -- В итоге рекомендоваться будут только треки с положительной суммой произведений рейтингов на коэффициенты + AND sum_rate > 0 --В итоге рекомендоваться будут только треки с положительной суммой произведений рейтингов на коэффициенты ORDER BY table2.sum_rate DESC LIMIT 1; RETURN preferenced_track; From f8ef8da6a83996086324921df5fcab18bc4f45de Mon Sep 17 00:00:00 2001 From: Igor Limanskij Date: Fri, 14 Apr 2017 11:59:12 +0300 Subject: [PATCH 40/44] Update ownradio_db_v3.sql --- dbScripts/ownradio_db_v3.sql | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/dbScripts/ownradio_db_v3.sql b/dbScripts/ownradio_db_v3.sql index 9d33f24..eb19fd0 100644 --- a/dbScripts/ownradio_db_v3.sql +++ b/dbScripts/ownradio_db_v3.sql @@ -1313,11 +1313,14 @@ BEGIN AND tracks.isexist = 1 --Трек должен существовать на сервере AND tracks.iscensorial <> 0 --Трек не должен быть помечен как нецензурный AND tracks.length >= 120 - --Трек не должен был выдаваться в течении последних двух месяцев + --Трек не должен был выдаваться исходному пользователю в течении последних двух месяцев AND tracks.recid NOT IN (SELECT trackid FROM downloadtracks - WHERE reccreated > localtimestamp - INTERVAL '2 months') + WHERE reccreated > localtimestamp - INTERVAL '2 months' AND deviceid = in_userid) AND sum_rate > 0 --В итоге рекомендоваться будут только треки с положительной суммой произведений рейтингов на коэффициенты ORDER BY table2.sum_rate DESC + --Сортировка по второму столбцу нужна для случаев, когда получаем много треков с одинковым table2.sum_rate, + --в таких случаях план выполнения запроса меняется и производительность сильно падает + ,tracks.recid LIMIT 1; RETURN preferenced_track; END; @@ -1326,3 +1329,4 @@ $BODY$ COST 100; ALTER FUNCTION public.getrecommendedtrackid_v1(uuid) OWNER TO postgres; + From 95c63e86790b785454b4d6e9fe3de44597f93d2e Mon Sep 17 00:00:00 2001 From: Alexandra Polunina Date: Fri, 14 Apr 2017 14:45:26 +0300 Subject: [PATCH 41/44] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8?= =?UTF-8?q?=D0=BB=D0=B0=20=D0=B2=D1=8B=D0=B2=D0=BE=D0=B4=20=D0=B2=D1=80?= =?UTF-8?q?=D0=B5=D0=BC=D0=B5=D0=BD=D0=B8=20=D0=B2=D1=8B=D0=BF=D0=BE=D0=BB?= =?UTF-8?q?=D0=BD=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=B7=D0=B0=D0=BF=D1=80=D0=BE?= =?UTF-8?q?=D1=81=D0=B0=20=D1=81=D0=BB=D0=B5=D0=B4=D1=83=D1=8E=D1=89=D0=B5?= =?UTF-8?q?=D0=B3=D0=BE=20=D1=82=D1=80=D0=B5=D0=BA=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/ownradio/domain/NextTrack.java | 2 + .../ownradio/repository/TrackRepository.java | 4 +- .../service/impl/TrackServiceImpl.java | 1 + .../ownradio/web/rest/v4/TrackController.java | 15 +---- .../main/resources/data/postgresql/schema.sql | 65 ++++++++++++++++++- 5 files changed, 72 insertions(+), 15 deletions(-) diff --git a/v3/src/main/java/ownradio/domain/NextTrack.java b/v3/src/main/java/ownradio/domain/NextTrack.java index 74a5d01..906b320 100644 --- a/v3/src/main/java/ownradio/domain/NextTrack.java +++ b/v3/src/main/java/ownradio/domain/NextTrack.java @@ -3,6 +3,7 @@ import lombok.Getter; import lombok.Setter; +import java.sql.Time; import java.util.UUID; /** @@ -15,4 +16,5 @@ public class NextTrack { private Integer methodid; private UUID useridrecommended; private String txtrecommendedinfo; + private String timeexecute; } diff --git a/v3/src/main/java/ownradio/repository/TrackRepository.java b/v3/src/main/java/ownradio/repository/TrackRepository.java index db79e06..c0c3b77 100644 --- a/v3/src/main/java/ownradio/repository/TrackRepository.java +++ b/v3/src/main/java/ownradio/repository/TrackRepository.java @@ -16,8 +16,8 @@ public interface TrackRepository extends JpaRepository { @Query(value = "select getnexttrackid_string(?1)", nativeQuery = true) UUID getNextTrackId(UUID deviceId); -// @Query(value = "select * from getnexttrack_v2(?1)", nativeQuery = true) - @Query(value = "select * from getnexttrack(?1)", nativeQuery = true) + @Query(value = "select * from getnexttrack_v2(?1)", nativeQuery = true) +// @Query(value = "select * from getnexttrack(?1)", nativeQuery = true) List getNextTrackV2(UUID deviceId); @Query(value = "select registertrack(?1, ?2, ?3, ?4)", nativeQuery = true) diff --git a/v3/src/main/java/ownradio/service/impl/TrackServiceImpl.java b/v3/src/main/java/ownradio/service/impl/TrackServiceImpl.java index 25d35c8..cbe381e 100644 --- a/v3/src/main/java/ownradio/service/impl/TrackServiceImpl.java +++ b/v3/src/main/java/ownradio/service/impl/TrackServiceImpl.java @@ -50,6 +50,7 @@ public NextTrack getNextTrackIdV2(UUID deviceId) { nextTrack.setMethodid((Integer) objects.get(0)[1]); if(objects.get(0)[2] != null) nextTrack.setUseridrecommended(UUID.fromString((String) objects.get(0)[2])); if(objects.get(0)[3] != null) nextTrack.setTxtrecommendedinfo((String) objects.get(0)[3]); + if(objects.get(0)[4] != null) nextTrack.setTimeexecute((String) objects.get(0)[4]); return nextTrack; }else{ return null; diff --git a/v3/src/main/java/ownradio/web/rest/v4/TrackController.java b/v3/src/main/java/ownradio/web/rest/v4/TrackController.java index a66790d..d999e7a 100644 --- a/v3/src/main/java/ownradio/web/rest/v4/TrackController.java +++ b/v3/src/main/java/ownradio/web/rest/v4/TrackController.java @@ -9,7 +9,6 @@ import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; import ownradio.domain.Device; -import ownradio.domain.DownloadTrack; import ownradio.domain.NextTrack; import ownradio.domain.Track; import ownradio.repository.DownloadTrackRepository; @@ -104,7 +103,8 @@ public ResponseEntity getNextTrack(@PathVariable UUID deviceId) { try { nextTrack = trackService.getNextTrackIdV2(deviceId); }catch (Exception ex){ - + log.info("{}", ex.getMessage()); + return new ResponseEntity<>(HttpStatus.NOT_FOUND); } @@ -112,16 +112,6 @@ public ResponseEntity getNextTrack(@PathVariable UUID deviceId) { if (nextTrack != null) { try { Track track = trackRepository.findOne(nextTrack.getTrackid()); - //Сохраняем информацию об отданном треке -// Device device = new Device(); -// device.setRecid(deviceId); -// DownloadTrack downloadTrack = new DownloadTrack(); -// downloadTrack.setTrack(track); -// downloadTrack.setDevice(device); -// downloadTrack.setMethodid(nextTrack.getMethodid()); -// downloadTrack.setUserrecommend(nextTrack.getUseridrecommended()); -// downloadTrack.setTxtrecommendinfo(nextTrack.getTxtrecommendedinfo()); -// downloadTrackRepository.saveAndFlush(downloadTrack); File file = new File(track.getPath()); if(!file.exists()){ @@ -150,6 +140,7 @@ public ResponseEntity getNextTrack(@PathVariable UUID deviceId) { trackInfo.put("artist", "Unknown artist"); trackInfo.put("pathupload", track.getLocaldevicepathupload()); + trackInfo.put("timeexecute", nextTrack.getTimeexecute()); log.info("getNextTrack return {} {}", nextTrack.getMethodid(), trackInfo.get("id")); diff --git a/v3/src/main/resources/data/postgresql/schema.sql b/v3/src/main/resources/data/postgresql/schema.sql index af26be7..a61f31b 100644 --- a/v3/src/main/resources/data/postgresql/schema.sql +++ b/v3/src/main/resources/data/postgresql/schema.sql @@ -920,4 +920,67 @@ BEGIN LIMIT i_count; END; ' -LANGUAGE plpgsql; \ No newline at end of file +LANGUAGE plpgsql; + + + + +CREATE OR REPLACE FUNCTION getnexttrack_v2(IN i_deviceid uuid) + RETURNS TABLE(track character varying, method integer, useridrecommended character varying, txtrecommendedinfo character varying, timeexecute character varying) AS +' +DECLARE + declare t timestamptz := clock_timestamp(); -- запоминаем начальное время выполнения процедуры + i_userid UUID = i_deviceid; -- в дальнейшем заменить получением userid по deviceid +BEGIN + -- Добавляем устройство, если его еще не существует + -- Если ID устройства еще нет в БД + IF NOT EXISTS(SELECT recid + FROM devices + WHERE recid = i_deviceid) + THEN + + -- Добавляем нового пользователя + INSERT INTO users (recid, recname, reccreated) SELECT + i_userid, + ''New user recname'', + now() + WHERE NOT EXISTS(SELECT recid FROM users WHERE recid = i_userid); + + -- Добавляем новое устройство + INSERT INTO devices (recid, userid, recname, reccreated) SELECT + i_deviceid, + i_userid, + ''New device recname'', + now(); + ELSE + SELECT (SELECT userid + FROM devices + WHERE recid = i_deviceid + LIMIT 1) + INTO i_userid; + END IF; + + -- Возвращаем trackid, конвертируя его в character varying, и methodid + RETURN QUERY SELECT + CAST((nexttrack.track) AS CHARACTER VARYING), + nexttrack.methodid, + CAST((nexttrack.useridrecommended) AS CHARACTER VARYING), + nexttrack.txtrecommendedinfo, + CAST((clock_timestamp() - t ) AS CHARACTER VARYING) -- возвращаем время выполнения процедуры + FROM getnexttrackid_v13(i_deviceid) AS nexttrack; +END; +' +LANGUAGE plpgsql; + +ALTER FUNCTION getnexttrack_v2(uuid) OWNER TO postgres; +ALTER FUNCTION calculateratios() OWNER TO postgres; +ALTER FUNCTION updateratios(uuid) OWNER TO postgres; +ALTER FUNCTION getlastdevices() OWNER TO postgres; +ALTER FUNCTION getlasttracks(uuid, integer) OWNER TO postgres; +ALTER FUNCTION getnexttrack(uuid) OWNER TO postgres; +ALTER FUNCTION getnexttrackid(uuid) OWNER TO postgres; +ALTER FUNCTION gettrackshistorybydevice(uuid, integer) OWNER TO postgres; +ALTER FUNCTION getuserdevices(uuid) OWNER TO postgres; +ALTER FUNCTION getusersrating(integer) OWNER TO postgres; +ALTER FUNCTION registerdevice(uuid, character varying) OWNER TO postgres; +ALTER FUNCTION registertrack(uuid, character varying, character varying, uuid) OWNER TO postgres; \ No newline at end of file From 1b7347d7c416831b256b84689f61ce6b285cbfb3 Mon Sep 17 00:00:00 2001 From: Alexandra Polunina Date: Tue, 18 Apr 2017 12:48:35 +0300 Subject: [PATCH 42/44] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8?= =?UTF-8?q?=D0=BB=D0=B0=20=D1=81=D0=BE=D1=85=D1=80=D0=B0=D0=BD=D0=B5=D0=BD?= =?UTF-8?q?=D0=B8=D0=B5=20historyId?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 29 ++++++++++++++- .../java/ownradio/domain/AbstractEntity.java | 11 ++++-- v3/src/main/java/ownradio/domain/History.java | 4 +++ v3/src/main/java/ownradio/domain/User.java | 1 + .../java/ownradio/service/HistoryService.java | 3 ++ .../service/impl/HistoryServiceImpl.java | 7 ++++ .../web/rest/v4/HistoryController.java | 36 +++++++++++++++---- .../repository/HistoryRepositoryTest.java | 2 +- 8 files changed, 82 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index ad3d9ab..8f45f2a 100644 --- a/README.md +++ b/README.md @@ -71,6 +71,29 @@ Content-Type →application/json;charset=UTF-8 Web API v4 --- +### Загрузка трека на сервер + +##### POST /v4/tracks +* `fileGuid` – UUID трека +* `filePath` - Полный локальный путь к файлу на пользовательском устройстве, включая имя файла (String) +* `deviceId` – UUID device +* `musicFile` – прикрепленный файл + +##### HttpStatus +* `400, "Bad Request"` - Если пользователь ввел некорректные данные +* `201, "Created"` – если все ок +* `500, "Internal Server Error"` – если произошел сбой на сервере + + +### Получение трека с сервера +##### GET /v4/tracks/{trackId} +* `{trackId}` – UUID трека + +##### HttpStatus +* `200, "OK"` – в теле ответа будет лежать трек +* `404, "Not Found"` – если трек с таким recid не найден + + ### Регистрация нового устройства ##### GET /v4/devices/{deviceId}/{deviceName}/registerdevice * `{deviceId}` - UUID устройства @@ -90,11 +113,14 @@ Content-Type →application/json;charset=UTF-8 "artist": "Artist", "length": "duration", "name": "Title", - "id": "00000000-0000-0000-0000-000000000000" + "id": "00000000-0000-0000-0000-000000000000", + "pathupload":"C:\\music\\track.mp3", + "timeexecute": "00:00:00.006238" } ### Сохранение истории треков ##### POST /v4/histories/{deviceId}/{trackId} +* `{historyId}` - UUID записи истории в БД устройства * `{trackId}` – UUID прослушанного трека * `{deviceId}` – UUID устройства где был прослушан трек * `lastListen` - Время последнего прослушивания или пропуска трека для данного пользователя ("yyyy-MM-ddTHH:mm:ss") @@ -102,5 +128,6 @@ Content-Type →application/json;charset=UTF-8 ##### HttpStatus * `200, "OK"` – если все ок +* `208, "Already Reported"` - если история с таким UUID уже отдавалась * `404, "Not found"` - если deviceId или trackid не найден * `500, "Internal Server Error"` – если произошел сбой на сервере diff --git a/v3/src/main/java/ownradio/domain/AbstractEntity.java b/v3/src/main/java/ownradio/domain/AbstractEntity.java index 991ae20..389ea09 100644 --- a/v3/src/main/java/ownradio/domain/AbstractEntity.java +++ b/v3/src/main/java/ownradio/domain/AbstractEntity.java @@ -2,13 +2,17 @@ import lombok.Getter; import lombok.Setter; +import org.hibernate.HibernateException; import org.hibernate.annotations.GenericGenerator; +import org.hibernate.engine.spi.SessionImplementor; +import org.hibernate.id.AbstractUUIDGenerator; +import org.hibernate.id.UUIDGenerationStrategy; +import org.hibernate.id.UUIDGenerator; import ownradio.annotation.DisplayName; import javax.persistence.*; import java.io.Serializable; import java.util.Calendar; -import java.util.Date; import java.util.UUID; /** @@ -25,8 +29,9 @@ public abstract class AbstractEntity implements Serializable { @DisplayName(key = "id") @Id @GeneratedValue(generator = "uuid") - @GenericGenerator(name = "uuid", strategy = "uuid2") + @GenericGenerator(name = "uuid", strategy="ownradio.util.IdOrGenerate") @Column(unique = true) +// @Column(insertable=true, updatable=true, unique=true, nullable=false) private UUID recid; private String recname; @@ -62,4 +67,4 @@ public boolean equals(Object o) { public int hashCode() { return recid != null ? recid.hashCode() : 0; } -} +} \ No newline at end of file diff --git a/v3/src/main/java/ownradio/domain/History.java b/v3/src/main/java/ownradio/domain/History.java index 679afa3..01497e3 100644 --- a/v3/src/main/java/ownradio/domain/History.java +++ b/v3/src/main/java/ownradio/domain/History.java @@ -44,4 +44,8 @@ public class History extends AbstractEntity { @ManyToOne @JoinColumn(name = "deviceid") private Device device; + + private Integer countsend; + + private String comment; } diff --git a/v3/src/main/java/ownradio/domain/User.java b/v3/src/main/java/ownradio/domain/User.java index 1f1c8c9..e0d4117 100644 --- a/v3/src/main/java/ownradio/domain/User.java +++ b/v3/src/main/java/ownradio/domain/User.java @@ -21,4 +21,5 @@ public class User extends AbstractEntity { public User(String name) { setRecname(name); } + public Integer experience; } diff --git a/v3/src/main/java/ownradio/service/HistoryService.java b/v3/src/main/java/ownradio/service/HistoryService.java index 6cccb29..ae2cc60 100644 --- a/v3/src/main/java/ownradio/service/HistoryService.java +++ b/v3/src/main/java/ownradio/service/HistoryService.java @@ -2,6 +2,8 @@ import ownradio.domain.History; +import java.util.UUID; + /** * Интерфейс сервиса, для работы с историей прослушанных треков * @@ -9,4 +11,5 @@ */ public interface HistoryService { void save(History history); + History getById(UUID uuid); } diff --git a/v3/src/main/java/ownradio/service/impl/HistoryServiceImpl.java b/v3/src/main/java/ownradio/service/impl/HistoryServiceImpl.java index bde07df..c96f892 100644 --- a/v3/src/main/java/ownradio/service/impl/HistoryServiceImpl.java +++ b/v3/src/main/java/ownradio/service/impl/HistoryServiceImpl.java @@ -10,6 +10,8 @@ import ownradio.repository.RatioRepository; import ownradio.service.HistoryService; +import java.util.UUID; + @Service public class HistoryServiceImpl implements HistoryService { @@ -24,6 +26,11 @@ public HistoryServiceImpl(HistoryRepository historyRepository, RatingRepository this.ratioRepository = ratioRepository; } + @Override + public History getById(UUID uuid) { + return historyRepository.findOne(uuid); + } + @Transactional @Override public void save(History history) { diff --git a/v3/src/main/java/ownradio/web/rest/v4/HistoryController.java b/v3/src/main/java/ownradio/web/rest/v4/HistoryController.java index 1d79a63..28d2b43 100644 --- a/v3/src/main/java/ownradio/web/rest/v4/HistoryController.java +++ b/v3/src/main/java/ownradio/web/rest/v4/HistoryController.java @@ -15,6 +15,7 @@ import java.text.SimpleDateFormat; import java.util.Calendar; +import java.util.Date; import java.util.UUID; /** @@ -81,7 +82,7 @@ private ResponseEntity getResponseEntity(@PathVariable UUID deviceId, @PathVaria return new ResponseEntity(HttpStatus.NOT_FOUND); log.info("deviceId:{} trackId: {}",deviceId.toString(),trackId.toString()); - log.info("{} {} {}",history.getLastListen(), history.getIsListen(), history.getMethodid()); + log.info("{} {} {} {}",history.getRecid(), history.getLastListen(), history.getIsListen(), history.getMethodid()); Track track = trackService.getById(trackId); Device device = deviceService.getById(deviceId); @@ -89,11 +90,34 @@ private ResponseEntity getResponseEntity(@PathVariable UUID deviceId, @PathVaria throw new RuntimeException("Track or Device is null"); } - history.setTrack(track); - history.setDevice(device); - - historyService.save(history); - log.info("Save history, rating and update ratios"); +// if(historyService.getById(history.getRecid()) == null) { +// History newHistory = history; +// newHistory.setRecid(newHistory.getRecid()); + if(history.getRecid() != null){ + History historyTemp = historyService.getById(history.getRecid()); + if(historyTemp != null){ + historyTemp.setCountsend(((historyTemp.getCountsend()==null?0:historyTemp.getCountsend())) + 1); +// historyTemp.setComment(historyTemp.getComment() + (new Date()).toString() + "; "); + historyService.save(historyTemp); + return new ResponseEntity(HttpStatus.ALREADY_REPORTED); + } else { + historyTemp = new History(); + historyTemp.setRecid(history.getRecid()); + historyTemp.setTrack(track); + historyTemp.setDevice(device); + historyTemp.setCountsend(1); + historyTemp.setIsListen(history.getIsListen()); + historyTemp.setLastListen(history.getLastListen()); + historyService.save(historyTemp); + } + } else { + history.setTrack(track); + history.setDevice(device); + history.setCountsend(1); + historyService.save(history); + } + log.info("Save history, rating and update ratios"); + //} return new ResponseEntity(HttpStatus.OK); } catch (Exception e) { return new ResponseEntity(HttpStatus.INTERNAL_SERVER_ERROR); diff --git a/v3/src/test/java/ownradio/repository/HistoryRepositoryTest.java b/v3/src/test/java/ownradio/repository/HistoryRepositoryTest.java index 4880970..d46e2aa 100644 --- a/v3/src/test/java/ownradio/repository/HistoryRepositoryTest.java +++ b/v3/src/test/java/ownradio/repository/HistoryRepositoryTest.java @@ -37,7 +37,7 @@ public void setUp() throws Exception { Device device = entityManager.persist(new Device(user, "1")); Track track = entityManager.persist(new Track("1", device, "1", 0, "", 0, null, null, null, 1)); - history = new History(track, Calendar.getInstance(), 0, "post", 1, device); + history = new History(track, Calendar.getInstance(), 0, "post", 1, device, 1, ""); entityManager.persist(history); } From d43a81f49a466d844cce2a9e747fb4a6947ce80c Mon Sep 17 00:00:00 2001 From: Alexandra Polunina Date: Tue, 18 Apr 2017 12:50:38 +0300 Subject: [PATCH 43/44] =?UTF-8?q?=D0=9A=D0=BB=D0=B0=D1=81=D1=81=20IdOrGene?= =?UTF-8?q?rate=20=D0=B7=D0=B0=D0=BF=D0=B8=D1=81=D1=8B=D0=B2=D0=B0=D0=B5?= =?UTF-8?q?=D1=82=20=D0=B2=20recid=20=D1=81=D1=83=D1=89=D0=B5=D1=81=D1=82?= =?UTF-8?q?=D0=B2=D1=83=D1=8E=D1=89=D0=B8=D0=B9=20UUID,=20=D0=B0=20=D0=B2?= =?UTF-8?q?=20=D1=81=D0=BB=D1=83=D1=87=D0=B0=D0=B5=20=D0=B5=D0=B3=D0=BE=20?= =?UTF-8?q?=D0=BE=D1=82=D1=81=D1=83=D1=82=D1=81=D1=82=D0=B2=D0=B8=D1=8F=20?= =?UTF-8?q?-=20=D0=B3=D0=B5=D0=BD=D0=B5=D1=80=D0=B8=D1=80=D1=83=D0=B5?= =?UTF-8?q?=D1=82=20=D0=BD=D0=BE=D0=B2=D1=8B=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/ownradio/util/IdOrGenerate.java | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 v3/src/main/java/ownradio/util/IdOrGenerate.java diff --git a/v3/src/main/java/ownradio/util/IdOrGenerate.java b/v3/src/main/java/ownradio/util/IdOrGenerate.java new file mode 100644 index 0000000..9313c93 --- /dev/null +++ b/v3/src/main/java/ownradio/util/IdOrGenerate.java @@ -0,0 +1,29 @@ +package ownradio.util; + +import org.hibernate.HibernateException; +import org.hibernate.engine.spi.SessionImplementor; +import org.hibernate.id.UUIDGenerator; +import ownradio.domain.AbstractEntity; + +import java.util.UUID; + +/** + * Created by a.polunina on 17.04.2017. + */ + + public class IdOrGenerate extends UUIDGenerator { + //private static final Logger log = Logger.getLogger(UseIdOrGenerate.class.getName()); + + @Override + public UUID generate(SessionImplementor session, Object obj) throws HibernateException { + if (obj == null) throw new HibernateException(new NullPointerException()); + + if (((AbstractEntity) obj).getRecid() == null) { + UUID id = UUID.randomUUID(); // super.generate(session, obj); + return id; + } else { + return ((AbstractEntity) obj).getRecid(); + + } + } + } \ No newline at end of file From 596ffa852b42233c36b5af2b5494fc6eeafc9165 Mon Sep 17 00:00:00 2001 From: Alexandra Polunina Date: Thu, 4 May 2017 23:47:23 +0300 Subject: [PATCH 44/44] =?UTF-8?q?=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=B8=D0=BB=D0=B0=20=D1=80=D0=B0=D1=81=D1=87=D0=B5=D1=82=20?= =?UTF-8?q?=D1=80=D0=B5=D0=B9=D1=82=D0=B8=D0=BD=D0=B3=D0=B0=20=D0=BF=D1=80?= =?UTF-8?q?=D0=B8=20=D0=BF=D0=BE=D0=B2=D1=82=D0=BE=D1=80=D0=BE=D0=BD=D0=BE?= =?UTF-8?q?=D0=B9=20=D0=BE=D1=82=D0=BF=D1=80=D0=B0=D0=B2=D0=BA=D0=B5=20?= =?UTF-8?q?=D0=B7=D0=B0=D0=BF=D0=B8=D1=81=D0=B8=20=D0=B8=D1=81=D1=82=D0=BE?= =?UTF-8?q?=D1=80=D0=B8=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- .../java/ownradio/domain/UsersRating.java | 6 ++- .../ownradio/repository/UserRepository.java | 3 ++ .../java/ownradio/service/HistoryService.java | 2 +- .../java/ownradio/service/UserService.java | 1 + .../service/impl/HistoryServiceImpl.java | 43 ++++++++++--------- .../service/impl/UserServiceImpl.java | 26 ++++++++++- .../main/java/ownradio/util/IdOrGenerate.java | 1 - .../web/rest/v2/HistoryController.java | 2 +- .../web/rest/v3/HistoryController.java | 2 +- .../web/rest/v4/HistoryController.java | 6 +-- .../web/rest/v4/StatisticsController.java | 10 +++++ .../resources/application-prod.properties | 3 +- .../main/resources/data/postgresql/schema.sql | 36 ++++++++-------- .../web/rest/v2/HistoryControllerTest.java | 3 +- 15 files changed, 94 insertions(+), 52 deletions(-) diff --git a/README.md b/README.md index 8f45f2a..fdce005 100644 --- a/README.md +++ b/README.md @@ -97,7 +97,7 @@ Web API v4 ### Регистрация нового устройства ##### GET /v4/devices/{deviceId}/{deviceName}/registerdevice * `{deviceId}` - UUID устройства -* `{deviceName}` - название устройства (может отсутсвтваовать) +* `{deviceName}` - название устройства (может отсутствовать) ##### HttpStatus * `200, "OK"` – если все ок * `400, "Bad Request"` - Если пользователь ввел некорректные данные diff --git a/v3/src/main/java/ownradio/domain/UsersRating.java b/v3/src/main/java/ownradio/domain/UsersRating.java index 2d2f25d..2dffb55 100644 --- a/v3/src/main/java/ownradio/domain/UsersRating.java +++ b/v3/src/main/java/ownradio/domain/UsersRating.java @@ -25,7 +25,11 @@ public class UsersRating{ // @Temporal(TemporalType.TIMESTAMP) private String recupdated; + @DateTimeFormat(pattern = "dd-MM-yyyy'T'H:m:s") + //@Temporal(TemporalType.TIMESTAMP) + private String lastactive; + private BigInteger owntracks; - private BigInteger lasttracks; + private BigInteger downloadtracks; } diff --git a/v3/src/main/java/ownradio/repository/UserRepository.java b/v3/src/main/java/ownradio/repository/UserRepository.java index 0e7beb8..04c426a 100644 --- a/v3/src/main/java/ownradio/repository/UserRepository.java +++ b/v3/src/main/java/ownradio/repository/UserRepository.java @@ -16,4 +16,7 @@ public interface UserRepository extends JpaRepository { @Query(value = "select * from getusersrating(?1)", nativeQuery = true) List getUsersRating(Integer countRows); + + @Query(value = "select * from getlastusers(?1)", nativeQuery = true) + List getLastUsers(Integer countRows); } diff --git a/v3/src/main/java/ownradio/service/HistoryService.java b/v3/src/main/java/ownradio/service/HistoryService.java index ae2cc60..11ed099 100644 --- a/v3/src/main/java/ownradio/service/HistoryService.java +++ b/v3/src/main/java/ownradio/service/HistoryService.java @@ -10,6 +10,6 @@ * @author Alpenov Tanat */ public interface HistoryService { - void save(History history); + void save(History history, Boolean isNewHistoryRec); History getById(UUID uuid); } diff --git a/v3/src/main/java/ownradio/service/UserService.java b/v3/src/main/java/ownradio/service/UserService.java index 2a78481..9e4392b 100644 --- a/v3/src/main/java/ownradio/service/UserService.java +++ b/v3/src/main/java/ownradio/service/UserService.java @@ -18,4 +18,5 @@ public interface UserService { List getUsersRating(Integer countRows); + List getLastUsers(Integer countRows); } diff --git a/v3/src/main/java/ownradio/service/impl/HistoryServiceImpl.java b/v3/src/main/java/ownradio/service/impl/HistoryServiceImpl.java index c96f892..c080d2e 100644 --- a/v3/src/main/java/ownradio/service/impl/HistoryServiceImpl.java +++ b/v3/src/main/java/ownradio/service/impl/HistoryServiceImpl.java @@ -33,29 +33,30 @@ public History getById(UUID uuid) { @Transactional @Override - public void save(History history) { + public void save(History history, Boolean isNewHistoryRec) { historyRepository.saveAndFlush(history); - Rating rating = ratingRepository.findByUserAndTrack(history.getDevice().getUser(), history.getTrack()); - if(rating != null) { - int ratingsum = rating.getRatingsum() + history.getIsListen(); - rating.setLastlisten(history.getLastListen()); - rating.setRatingsum(ratingsum); - ratingRepository.saveAndFlush(rating); - } - else { - rating = new Rating(); - rating.setUser(history.getDevice().getUser()); - rating.setTrack(history.getTrack()); - rating.setLastlisten(history.getLastListen()); - rating.setRatingsum(history.getIsListen()); - ratingRepository.saveAndFlush(rating); - } - - try { - ratioRepository.updateRatios(history.getDevice().getRecid()); - }catch (Exception ex){ - ex.printStackTrace(); + if(isNewHistoryRec) { + Rating rating = ratingRepository.findByUserAndTrack(history.getDevice().getUser(), history.getTrack()); + if (rating != null) { + int ratingsum = rating.getRatingsum() + history.getIsListen(); + rating.setLastlisten(history.getLastListen()); + rating.setRatingsum(ratingsum); + ratingRepository.saveAndFlush(rating); + } else { + rating = new Rating(); + rating.setUser(history.getDevice().getUser()); + rating.setTrack(history.getTrack()); + rating.setLastlisten(history.getLastListen()); + rating.setRatingsum(history.getIsListen()); + ratingRepository.saveAndFlush(rating); + } + + try { + ratioRepository.updateRatios(history.getDevice().getRecid()); + } catch (Exception ex) { + ex.printStackTrace(); + } } } } diff --git a/v3/src/main/java/ownradio/service/impl/UserServiceImpl.java b/v3/src/main/java/ownradio/service/impl/UserServiceImpl.java index c9be972..b07389f 100644 --- a/v3/src/main/java/ownradio/service/impl/UserServiceImpl.java +++ b/v3/src/main/java/ownradio/service/impl/UserServiceImpl.java @@ -3,6 +3,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import ownradio.domain.Device; import ownradio.domain.User; import ownradio.domain.UsersRating; import ownradio.repository.UserRepository; @@ -41,7 +42,7 @@ public List getUsersRating(Integer countRows) { userRating.setRecname((String) objects.get(i)[2]); userRating.setRecupdated((String) objects.get(i)[3]); userRating.setOwntracks((BigInteger) objects.get(i)[4]); - userRating.setLasttracks((BigInteger) objects.get(i)[5]); + userRating.setDownloadtracks((BigInteger) objects.get(i)[5]); usersRating.add(userRating); } @@ -50,4 +51,27 @@ public List getUsersRating(Integer countRows) { } return usersRating; } + + + @Override + public List getLastUsers(Integer countRows){ + List lastUsers = new ArrayList(); + List objects = userRepository.getLastUsers(countRows); + if (objects != null) { + for (int i = 0; i < objects.size(); i++) { + UsersRating lastUser = new UsersRating(); + lastUser.setUserid(UUID.fromString((String) objects.get(i)[0])); + lastUser.setReccreated((String) objects.get(i)[1]); + lastUser.setLastactive((String) objects.get(i)[2]); + lastUser.setRecname((String) objects.get(i)[3]); + lastUser.setRecupdated((String) objects.get(i)[4]); + lastUser.setOwntracks((BigInteger) objects.get(i)[5]); + lastUser.setDownloadtracks((BigInteger) objects.get(i)[6]); + lastUsers.add(lastUser); + } + } else { + return null; + } + return lastUsers; + } } diff --git a/v3/src/main/java/ownradio/util/IdOrGenerate.java b/v3/src/main/java/ownradio/util/IdOrGenerate.java index 9313c93..8d17e5d 100644 --- a/v3/src/main/java/ownradio/util/IdOrGenerate.java +++ b/v3/src/main/java/ownradio/util/IdOrGenerate.java @@ -12,7 +12,6 @@ */ public class IdOrGenerate extends UUIDGenerator { - //private static final Logger log = Logger.getLogger(UseIdOrGenerate.class.getName()); @Override public UUID generate(SessionImplementor session, Object obj) throws HibernateException { diff --git a/v3/src/main/java/ownradio/web/rest/v2/HistoryController.java b/v3/src/main/java/ownradio/web/rest/v2/HistoryController.java index 14a56b9..9c3093a 100644 --- a/v3/src/main/java/ownradio/web/rest/v2/HistoryController.java +++ b/v3/src/main/java/ownradio/web/rest/v2/HistoryController.java @@ -58,7 +58,7 @@ public ResponseEntity save(@PathVariable UUID deviceId, @PathVariable UUID track history.setTrack(track); history.setDevice(device); - historyService.save(history); + historyService.save(history, true); return new ResponseEntity(history, HttpStatus.OK); } catch (Exception e) { diff --git a/v3/src/main/java/ownradio/web/rest/v3/HistoryController.java b/v3/src/main/java/ownradio/web/rest/v3/HistoryController.java index 902a1dd..383394b 100644 --- a/v3/src/main/java/ownradio/web/rest/v3/HistoryController.java +++ b/v3/src/main/java/ownradio/web/rest/v3/HistoryController.java @@ -93,7 +93,7 @@ private ResponseEntity getResponseEntity(@PathVariable UUID deviceId, @PathVaria history.setTrack(track); history.setDevice(device); - historyService.save(history); + historyService.save(history, true); log.info("Save history, rating and update ratios"); return new ResponseEntity(HttpStatus.OK); } catch (Exception e) { diff --git a/v3/src/main/java/ownradio/web/rest/v4/HistoryController.java b/v3/src/main/java/ownradio/web/rest/v4/HistoryController.java index 28d2b43..017a41a 100644 --- a/v3/src/main/java/ownradio/web/rest/v4/HistoryController.java +++ b/v3/src/main/java/ownradio/web/rest/v4/HistoryController.java @@ -98,7 +98,7 @@ private ResponseEntity getResponseEntity(@PathVariable UUID deviceId, @PathVaria if(historyTemp != null){ historyTemp.setCountsend(((historyTemp.getCountsend()==null?0:historyTemp.getCountsend())) + 1); // historyTemp.setComment(historyTemp.getComment() + (new Date()).toString() + "; "); - historyService.save(historyTemp); + historyService.save(historyTemp, false); return new ResponseEntity(HttpStatus.ALREADY_REPORTED); } else { historyTemp = new History(); @@ -108,13 +108,13 @@ private ResponseEntity getResponseEntity(@PathVariable UUID deviceId, @PathVaria historyTemp.setCountsend(1); historyTemp.setIsListen(history.getIsListen()); historyTemp.setLastListen(history.getLastListen()); - historyService.save(historyTemp); + historyService.save(historyTemp, true); } } else { history.setTrack(track); history.setDevice(device); history.setCountsend(1); - historyService.save(history); + historyService.save(history, true); } log.info("Save history, rating and update ratios"); //} diff --git a/v3/src/main/java/ownradio/web/rest/v4/StatisticsController.java b/v3/src/main/java/ownradio/web/rest/v4/StatisticsController.java index be604f7..be3780a 100644 --- a/v3/src/main/java/ownradio/web/rest/v4/StatisticsController.java +++ b/v3/src/main/java/ownradio/web/rest/v4/StatisticsController.java @@ -95,5 +95,15 @@ public ResponseEntity getLastDevices() { return new ResponseEntity<>(HttpStatus.NOT_FOUND); } } + + @RequestMapping(value = "/getlastusers/{countUsers}", method = RequestMethod.GET) + public ResponseEntity getLastUsers(@PathVariable Integer countUsers) { + try{ + List lastActiveDevices = userService.getLastUsers(countUsers); + return new ResponseEntity<>(lastActiveDevices, HttpStatus.OK); + }catch (Exception ex){ + return new ResponseEntity<>(HttpStatus.NOT_FOUND); + } + } } diff --git a/v3/src/main/resources/application-prod.properties b/v3/src/main/resources/application-prod.properties index ef6b7df..ce125c6 100644 --- a/v3/src/main/resources/application-prod.properties +++ b/v3/src/main/resources/application-prod.properties @@ -21,5 +21,6 @@ logging.level.ownradio=info logging.level.org.springframework=warn logging.level.org.hibernate=warn #server.port = 9090 -spring.datasource.schema=classpath:/data/postgresql/schema.sql +#There is switch on file with sql scripts for auto create stored procedures +#spring.datasource.schema=classpath:/data/postgresql/schema.sql #spring.datasource.data=classpath:/data/postgresql/data.sql \ No newline at end of file diff --git a/v3/src/main/resources/data/postgresql/schema.sql b/v3/src/main/resources/data/postgresql/schema.sql index a61f31b..3a245d3 100644 --- a/v3/src/main/resources/data/postgresql/schema.sql +++ b/v3/src/main/resources/data/postgresql/schema.sql @@ -252,9 +252,8 @@ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION calculateratios() RETURNS boolean AS ' --- Функция рассчитывает таблицу коэффициентов схожести интересов для пар пользователей DECLARE - -- объявляем курсор и запрос для него +-- объявляем курсор и запрос для него curs1 CURSOR FOR SELECT * FROM( -- рассчитываем матрицу коэффициентов схожести интересов для каждой пары пользователей SELECT r.userid as userid01, r2.userid as userid02, SUM(r.ratingsum * r2.ratingsum) as s @@ -331,27 +330,25 @@ BEGIN IF NOT FOUND THEN EXIT; -- если данных нет - выходим END IF; - -- если для данной пары пользователей уже записан коэффициент - пропускаем, иначе - записываем во временную таблицу - IF NOT EXISTS (SELECT * FROM temp_ratio WHERE userid1 = cuser2 AND userid2 = cuser1 OR userid1 = cuser1 AND userid2 = cuser2) THEN - INSERT INTO temp_ratio(userid1, userid2, ratio) + + INSERT INTO temp_ratio(userid1, userid2, ratio) VALUES (cuser1, cuser2, cratio); - END IF; + END LOOP; CLOSE curs1; -- закрываем курсор - -- обновляем имеющиеся коэффициенты в таблице ratios UPDATE ratios SET ratio = temp_ratio.ratio, recupdated = now() FROM temp_ratio - WHERE (ratios.userid1 = temp_ratio.userid1 AND ratios.userid2 = temp_ratio.userid2) - OR (ratios.userid1 = temp_ratio.userid2 AND ratios.userid2 = temp_ratio.userid1); + WHERE ratios.userid1 = temp_ratio.userid1 AND ratios.userid2 = temp_ratio.userid2; + + + INSERT INTO ratios (userid1, userid2, ratio, reccreated) + (SELECT temp_ratio.userid1,temp_ratio.userid2, temp_ratio.ratio, now() + FROM temp_ratio + LEFT OUTER JOIN ratios ON + temp_ratio.userid1 = ratios.userid1 AND temp_ratio.userid2 = ratios.userid2 + WHERE ratios.userid1 IS NULL OR ratios.userid2 IS NULL + ); - -- если в ratios меньше пар пользователей, чем во временной таблице - вставляем недостающие записи - IF (SELECT COUNT(*) FROM ratios WHERE userid1 = i_userid or userid2 = i_userid) < (SELECT COUNT(*) FROM temp_ratio) THEN - INSERT INTO ratios (userid1, userid2, ratio, reccreated) - (SELECT tr.userid1, tr.userid2, tr.ratio, now() FROM temp_ratio AS tr - LEFT OUTER JOIN ratios AS rr ON tr.userid1 = rr.userid1 AND tr.userid2 = rr.userid2 OR tr.userid1 = rr.userid2 AND tr.userid2 = rr.userid1 - WHERE rr.userid1 IS NULL OR rr.userid2 IS NULL - ); - END IF; RETURN TRUE; END; ' @@ -578,7 +575,7 @@ BEGIN IF i_count < 0 THEN i_count = null; END IF; -RETURN QUERY SELECT CAST((res1.recid) AS CHARACTER VARYING), CAST((res1.reccreated) AS CHARACTER VARYING), res1.recname, CAST((res1.recupdated) AS CHARACTER VARYING), res1.owntracks, COUNT(res2.userid) AS lasttracks +RETURN QUERY SELECT CAST((res1.recid) AS CHARACTER VARYING), CAST((res1.reccreated) AS CHARACTER VARYING), res1.recname, CAST((res1.recupdated) AS CHARACTER VARYING), res1.owntracks, COUNT(res2.userid) AS downloadtracks FROM (SELECT u.recid, u.reccreated, u.recname, u.recupdated, COUNT(r.recid) AS owntracks FROM users u @@ -589,7 +586,7 @@ FROM ON dev.recid= d.deviceid AND d.reccreated > localtimestamp - INTERVAL ''1 day'') res2 ON res2.userid = res1.recid GROUP BY res1.recid, res1.reccreated, res1.recname, res1.recupdated, res1.owntracks - ORDER BY lasttracks DESC, owntracks DESC + ORDER BY downloadtracks DESC, owntracks DESC LIMIT i_count; END; @@ -924,6 +921,7 @@ LANGUAGE plpgsql; +DROP FUNCTION getnexttrack_v2(uuid); CREATE OR REPLACE FUNCTION getnexttrack_v2(IN i_deviceid uuid) RETURNS TABLE(track character varying, method integer, useridrecommended character varying, txtrecommendedinfo character varying, timeexecute character varying) AS diff --git a/v3/src/test/java/ownradio/web/rest/v2/HistoryControllerTest.java b/v3/src/test/java/ownradio/web/rest/v2/HistoryControllerTest.java index be5ae70..97c2273 100644 --- a/v3/src/test/java/ownradio/web/rest/v2/HistoryControllerTest.java +++ b/v3/src/test/java/ownradio/web/rest/v2/HistoryControllerTest.java @@ -24,6 +24,7 @@ import static org.mockito.BDDMockito.given; import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyBoolean; import static org.mockito.Mockito.doThrow; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; @@ -94,7 +95,7 @@ public void saveStatusIsInternalServerError() throws Exception { given(this.trackService.getById(TRACK_UUID)).willReturn(track); given(this.deviceService.getById(DEVICE_UUID)).willReturn(device); - doThrow(RuntimeException.class).when(this.historyService).save(any(History.class)); + doThrow(RuntimeException.class).when(this.historyService).save(any(History.class), anyBoolean()); JSONObject obj = new JSONObject(); obj.put("lastListen", "2016-11-28T12:34:56");