Isipin natin na nagtatrabaho ka sa iyong web application. Sa aking mga artikulo tinitingnan ko ang mga indibidwal na piraso ng mosaic na ito, tulad ng:
Paano kapaki-pakinabang ang mga paksang ito? At ang katotohanan na ang mga halimbawang ito ay napakalapit sa paggawa sa mga tunay na proyekto, at ang pagsubok sa mga paksang ito ay magiging lubhang kapaki-pakinabang para sa iyo. Ngayon ay kukunin natin ang susunod na piraso ng mosaic na ito - nagtatrabaho sa mga file, dahil sa ngayon ay hindi ka na makakahanap ng isang site na hindi nakikipag-ugnayan sa kanila (halimbawa, lahat ng uri ng mga web shop, mga social network, at iba pa). Ang pagsusuri ay isasagawa gamit ang mga paraan ng pag-upload/pag-download/pagtanggal bilang isang halimbawa ; ise-save namin ito sa isang folder (sa mapagkukunan) sa aming aplikasyon, upang hindi ito kumplikado. Ang ideya ay ise-save namin, bilang karagdagan sa file mismo sa aming tinatawag na file storage, isang entity na may impormasyon tungkol sa aming file (laki, pangalan, atbp.) sa aming database - isang pre-created na talahanayan. Iyon ay, kapag naglo-load ng isang file, ang entity na ito ay magiging kapaki-pakinabang sa amin, at kapag nagtanggal, hindi namin dapat kalimutan ang tungkol dito sa anumang paraan. Tingnan natin ang talahanayang ito: At itakda natin ang id sa AUTO_INCREMENT tulad ng mga nasa hustong gulang, upang awtomatikong bumuo ng isang identifier sa antas ng database. Una, tingnan natin ang aming istraktura: Ang entity sa ilalim ng talahanayan na ipinapakita sa itaas:
Mga kawili-wiling bagay: 9 - Tinatanggap namin ang file sa form MultipartFile. Maaari din itong matanggap bilang isang hanay ng mga byte, ngunit mas gusto ko ang opsyong ito, dahil maaari nating MultipartFilekunin ang iba't ibang katangian ng inilipat na file. 10 - 14 - binabalot namin ang aming mga aksyon try catchupang kung may maganap na pagbubukod sa mas mababang antas, ipinapasa namin ito nang mas mataas at nagpapadala ng 400 na error bilang tugon. Susunod ay ang antas ng serbisyo:
8 - sa kaso ng isang IOException, ang lahat ng aming mga pag-save sa database ay ibabalik. 11 - bumubuo kami ng isang susi na magiging kakaiba para sa file kapag na-save ito (kahit na naka-save ang dalawang file na may parehong mga pangalan, hindi magkakaroon ng kalituhan). 12 — bumuo kami ng isang entity upang i-save sa database. 17 — hinihimok namin ang entity na may impormasyon sa database. 18 - i-save ang file na may hashed na pangalan. 20 — ibinabalik namin ang nilikhang entity FileInfo, ngunit kasama ang nabuong id sa database (pag-uusapan natin ito sa ibaba) at ang petsa ng paglikha. Paraan para sa pagbuo ng isang susi sa isang file:
11 — создаём date которую и сохраним. 12 - 21 — сохраняем сущность, но более сложным путем, с явным созданием an object PreparedStatement, чтобы можно было вытащить сгенерированный id (он его вытягивает не отдельным requestом, а в виде ответных метаданных). 22 - 26 — достраиваем нашу многострадальную сущность и отдаем наверх (на самом деле он его не достраивает, а создаёт новый an object, заполняя переданные поля и копируя остальные с изначального). Давайте посмотрим, How будут сохраняться наши файлы в FileManager:
1 — принимаем файл в виде массива byteов и отдельно Name, под которым он будет сохранен (наш сгенерированный ключ). 2 - 3 — создаем путь (а в пути прописываем путь плюс наш ключ) и файл по нему. 6 - 7 — создаем поток и пишем туда наши byteы (и оборачиваем это все добро в try-finally чтобы быть уверенными, что поток точно закроется). Тем не менее, многие из методов могут нам выкинуть IOException. В таком случае, благодаря прописанной в шапке метода проброске, мы прокинем его в контроллер и отдадим 400 статус. Давайте протестируем все это дело в Postman: Как видим, все отлично, ответ 201, ответный JSON пришел в виде нашей сохраняемой сущности в БД, и если заглянем в наше хранorще: БД: увидим, что у нас появилось новое meaning. (=*;*=)
Так же оборачиваем в try-catch и в случае IOException отправляем 404 статус ответа (не найдено). 4 — вытягиваем из БД прилежащую сущность FileInfo. 5 — по ключу из сущности скачиваем файл 6-8 — отправляем назад файл, при этом добавив в хедер file name (опять же, полученное из сущности с информацией о файле). Давайте посмотрим глубже. Интерфейс сервиса:
Тут особо интересного ничего нет: метод поиска сущности по id и загрузка file, разве что 46 — помечаем, что транзакция у нас для чтения. Уровень dao:
FileInfofindById(Long fileId);
Имплементация:
privatestaticfinalString FIND_FILE_BY_ID ="SELECT id, file_name, file_size, file_key, upload_date FROM files_info WHERE id = ?";@OverridepublicFileInfofindById(Long fileId){return jdbcTemplate.queryForObject(FIND_FILE_BY_ID,rowMapper(), fileId);}privateRowMapper<FileInfo>rowMapper(){return(rs, rowNum)->FileInfo.builder().id(rs.getLong("id")).name(rs.getString("file_name")).size(rs.getLong("file_size")).key(rs.getString("file_key")).uploadDate(rs.getObject("upload_date",LocalDate.class)).build();}
4 — поиск по id c использованием jdbcTemplate и RowMapper. 8 - 15 — реализация RowMapper для нашего конкретного случая, для сопоставления данных из БД и полей модели. Идем в FileManager и смотрим, How загружается наш файл:
Возвращаем файл в виде an object Resource, а искать будем по ключу. 3 — создаем Resource по пути + ключ. 4 - 8 — проверяем, что файл по заданному пути не пуст и читаем. Если всё ОК, возвращаем его, а если нет, прокидываем IOException наверх. Проверяем наш метод в Postman: Как видим, он отработал на ОК))
1 — также откат изменения данных (удаления) при падении IOException. 5 — удаляем информацию о файле из БД. 6 — удаляем сам файл из нашего “хранorща”. Интерфейс dao:
voiddelete(Long fileId);
Реализация:
privatestaticfinalString DELETE_FILE_BY_ID ="DELETE FROM files_info WHERE id = ?";@Overridepublicvoiddelete(Long fileId){
jdbcTemplate.update(DELETE_FILE_BY_ID, fileId);}
Ничего такого — просто delete. Удаление самого file:
Юзаем в Postman: Смотрим в хранorще: Пусто :) Теперь в БД: Видим, что все good))
Test
Давайте попробуем написать тест под наш FileManager. Для начала взглянем на структуру тестовой части: mockFile.txt — это файл, с помощью которого мы будем тестить наши операции с file storage. testFileStorage будет заменой нашего хранorща. FileManagerTest:
3 — с помощью тестовой рефлексии меняем нашу константу в сервисе для задания пути сохранения file. 5 — вызываем проверяемый метод. 7 - 10 — проверяем правильность исполнения сохранения. 11 — удаляем сохраненный файл (мы не должны оставить ниHowих следов).
Тест загрузки file: 3 — опять же, меняем путь для нашего FileManager. 5 — юзаем проверяемый метод. 7 - 9 — проверяем результат исполнения. Тест удаления file:
9 - 3 - 4 — задаем путь и создаем файл. 5 - 6 — проверяем его существование. 9 — используем проверяемый метод. 77 — проверяем, что обьекта уже нет. И смотрим, что там у нас по зависимостям:
GO TO FULL VERSION