JavaRush /Java блогу /Random-KY /Файлдарды тиркемеге жана алар жөнүндө маалыматтарды маалы...
Константин
Деңгээл

Файлдарды тиркемеге жана алар жөнүндө маалыматтарды маалымат базасына сактоо

Группада жарыяланган
Файлдарды тиркемеге жана алар жөнүндө маалыматтарды маалымат базасына сактоо - 1 Келгиле, сиз веб-тиркемеңиздин үстүндө иштеп жатасыз деп элестетип көрөлү. Мен макалаларымда бул мозаиканын айрым бөлүктөрүн карайм, мисалы: Бул темалар кандайча пайдалуу? Жана бул мисалдар реалдуу долбоорлордун үстүндө иштөөгө абдан жакын экендиги жана бул темаларды сынап көрүү сиз үчүн абдан пайдалуу болот. Бүгүн биз бул мозаиканын кезектеги бөлүгүн алабыз - файлдар менен иштөө, анткени бүгүнкү күндө алар менен иштешпеген сайтты таба албайсыз (мисалы, ар кандай веб-дүкөндөр, социалдык тармактар ​​жана башкалар). Карап чыгуу мисал катары жүктөө/жүктөө/жок кылуу ыкмаларын колдонуу менен жүргүзүлөт ; аны татаалдаштырбоо үчүн аны тиркемедеги папкада (ресурста) сактайбыз. Тиркемеге файлдарды жана алар жөнүндө маалыматтарды маалымат базасына сактоо - 2Идея, биз файл сактагычыбыз деп аталган файлдын өзүнөн тышкары, биздин маалымат базабызда биздин файл жөнүндө маалыматы бар an objectти (өлчөмү, аты ж.б.) сактайбыз - алдын ала түзүлгөн table. Башкача айтканда, файлды жүктөөдө, бул an object биз үчүн абдан пайдалуу болот, жана жок кылууда, биз эч кандай жол менен бул жөнүндө унутпашыбыз керек. Келгиле, бул tableны карап көрөлү: Файлдарды тиркемеге жана алар жөнүндө маалыматтарды маалымат базасына сактоо - 3Келгиле, маалымат базасынын деңгээлинде идентификаторду автоматтык түрдө түзүү үчүн чоңдор сыяктуу эле IDди AUTO_INCREMENT деп коёлу. Биринчиден, түзүмүбүздү карап көрөлү: Файлдарды тиркемеге жана алар жөнүндө маалыматтарды маалымат базасына сактоо - 4Жогоруда көрсөтүлгөн table астындагы an object:
@Builder(toBuilder = true)
@Getter
@ToString
public class FileInfo {

   private Long id;

   private String name;

   private Long size;

   private String key;

   private LocalDate uploadDate;
}

Жүктөө

Келгиле, контроллерди карап көрөлү:
@RestController
@RequestMapping("/file")
@RequiredArgsConstructor
public class FileController {

   private final FileService fileService;

   @PostMapping
   public ResponseEntity<FileInfo> upload(@RequestParam MultipartFile attachment) {
       try {
           return new ResponseEntity<>(fileService.upload(attachment), HttpStatus.CREATED);
       } catch (IOException e) {
           return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
       }
   }
Кээ бир кызыктуу нерселер: 9 - Биз файлды формада кабыл алабыз MultipartFile. Аны byte массиви катары да кабыл алса болот, бирок мага бул параметр жакшыраак жагат, анткени биз MultipartFileөткөрүлүп берилген файлдын ар кандай касиеттерин чыгарып алабыз. 10 - 14 - биз иш-аракеттерибизди ороп алабыз try catch, эгерде өзгөчөлүк төмөнкү деңгээлде болсо, биз аны жогору карай жөнөтөбүз жана жооп катары 400 ката жөнөтөбүз. Кийинки кызмат деңгээл:
public interface FileService {

   FileInfo upload(MultipartFile resource) throws IOException;
Келгиле, ишке ашырууну карап көрөлү:
@Service
@RequiredArgsConstructor
public class FileServiceImpl implements FileService {

   private final FileDAO fileDAO;
   private final FileManager fileManager;

   @Transactional(rollbackFor = {IOException.class})
   @Override
   public FileInfo upload(MultipartFile resource) throws IOException {
       String key = generateKey(resource.getName());
       FileInfo createdFile = FileInfo.builder()
               .name(resource.getOriginalFilename())
               .key(key)
               .size(resource.getSize())
               .build();
       createdFile = fileDAO.create(createdFile);
       fileManager.upload(resource.getBytes(), key);

       return createdFile;
   }
8 - IOException болгон учурда, биздин базадагы бардык сактоолорубуз артка кайтарылат. 11 - биз файл сакталганда уникалдуу боло турган ачкычты түзөбүз (бир эле аталыштагы эки файл сакталса да, эч кандай башаламандык болбойт). 12 — биз маалымат базасында сактоо үчүн an object курабыз. 17 — биз маалымат базасы менен субъектти айдайбыз. 18 - файлды хэштелген ат менен сактаңыз. 20 — биз түзүлгөн an objectти кайтарабыз FileInfo, бирок маалымат базасында түзүлгөн id (бул төмөндө талкууланат) жана түзүлгөн күнү менен. Файлдын ачкычын түзүү ыкмасы:
private String generateKey(String name) {
   return DigestUtils.md5Hex(name + LocalDateTime.now().toString());
}
Бул жерде биз уникалдуулугубузду камсыз кылуу үчүн аты + түзүлгөн датасын хэш. Дао катмарынын интерфейси:
public interface FileDAO {

   FileInfo create(FileInfo file);
Аны ишке ашыруу:
@Repository
@RequiredArgsConstructor
public class FileDAOImpl implements FileDAO {

   private static final String CREATE_FILE = "INSERT INTO files_info(file_name, file_size, file_key, upload_date) VALUES (?, ?, ?, ?)";

   private final JdbcTemplate jdbcTemplate;

   @Override
   public FileInfo create(final FileInfo file) {
       LocalDate uploadDate = LocalDate.now();
       GeneratedKeyHolder keyHolder = new GeneratedKeyHolder();
       jdbcTemplate.update(x -> {
           PreparedStatement preparedStatement = x.prepareStatement(CREATE_FILE, Statement.RETURN_GENERATED_KEYS);
           preparedStatement.setString(1, file.getName());
           preparedStatement.setLong(2, file.getSize());
           preparedStatement.setString(3, file.getKey());
           preparedStatement.setDate(4, Date.valueOf(uploadDate));
           return preparedStatement;
       }, keyHolder);

       return file.toBuilder()
               .id(keyHolder.getKey().longValue())
               .uploadDate(uploadDate)
               .build();
   }
11 - биз сактай турган күндү түзүңүз. 12 - 21 - биз an objectти сактайбыз, бирок генерацияланган идентификаторду чыгарып алуу үчүн an objectти ачык түзүү менен татаалыраак жол менен PreparedStatement(ал өзүнчө өтүнүч менен эмес, жооп метадайындары түрүндө чыгарат) ). 22 - 26 - биз көптөн бери чыдамкай болгон an objectибиздин курулушун бүтүрүп, аны жогору жакка беребиз (чындыгында, ал аны бүтүрбөйт, бирок жаңы an object түзүп, өткөрүлүп берилген талааларды толтуруп, калганын түпнускасынан көчүрөт) . Келгиле, биздин файлдар кантип сакталаарын карап көрөлү FileManager:
public void upload(byte[] resource, String keyName) throws IOException {
   Path path = Paths.get(DIRECTORY_PATH, keyName);
   Path file = Files.createFile(path);
   FileOutputStream stream = null;
   try {
       stream = new FileOutputStream(file.toString());
       stream.write(resource);
   } finally {
       stream.close();
   }
}
1 — биз файлды byte массиви катары кабыл алабыз жана ал сактала турган атын өзүнчө алабыз (биздин түзүлгөн ачкыч). 2 - 3 - жолду түзүңүз (жана жолдо биз жолду жана ачкычыбызды жазабыз) жана анын боюнда файл. 6 - 7 - агым түзүп, ошол жерге биздин byteтарды жазыңыз (жана try-finallyагым сөзсүз жабыларына ишенүү үчүн мунун баарын ороп алыңыз). Бирок, көптөгөн ыкмалар IOException ыргыта алат. Бул учурда, ыкманын аталышында көрсөтүлгөн жөнөтүүнүн аркасында биз аны контроллерге өткөрүп, 400 статусун беребиз. Почтачыда бардыгын сынап көрөлү: Файлдарды тиркемеге жана алар жөнүндө маалыматтарды маалымат базасына сактоо - 5Көрүнүп тургандай, баары жакшы, жооп 201, жооп JSON маалымат базасындагы туруктуу an object түрүндө келди жана сактагычыбызды карап көрсөк: DB: Файлдарды тиркемеге жана алар жөнүндө маалыматтарды маалымат базасына сактоо - 6биз көрүп жатабыз бизде жаңы баалуулук бар. (=*;*=)

Жүктөп алуу

Контроллер:
@GetMapping(path = "/{id}", produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
public ResponseEntity<Resource> download(@PathVariable("id") Long id) {
   try {
       FileInfo foundFile = fileService.findById(id);
       Resource resource = fileService.download(foundFile.getKey());
       return ResponseEntity.ok()
               .header("Content-Disposition", "attachment; filename=" + foundFile.getName())
               .body(resource);
   } catch (IOException e) {
       return new ResponseEntity<>(HttpStatus.NOT_FOUND);
   }
}
Биз ошондой эле аны ороп алабыз try-catchжана IOException учурда 404 жооп статусун жөнөтөбүз (табылган жок). 4 - чектеш FileInfo an objectисин маалымат базасынан чыгарып алыңыз. 5 - an objectтин ачкычын колдонуп, файлды жүктөө 6-8 - файлды кайра жөнөтүү, файлдын аталышын темага кошуу (кайрадан, файл жөнүндө маалымат менен субъекттен алынган). Келгиле, тереңирээк карап көрөлү. Кызмат интерфейси:
Resource download(String key) throws IOException;

FileInfo findById(Long fileId);
Ишке ашыруу:
@Override
public Resource download(String key) throws IOException {
   return fileManager.download(key);
}

@Transactional(readOnly = true)
@Override
public FileInfo findById(Long fileId) {
   return fileDAO.findById(fileId);
}
Бул жерде өзгөчө кызыктуу эч нерсе жок: id боюнча an objectти издөө жана файлды жүктөө ыкмасы, балким 46дан башкасы - бизде окуу үчүн транзакция бар деп белгилейбиз. Дао деңгээли:
FileInfo findById(Long fileId);
Ишке ашыруу:
private static final String FIND_FILE_BY_ID = "SELECT id, file_name, file_size, file_key, upload_date FROM files_info WHERE id = ?";

@Override
public FileInfo findById(Long fileId) {
   return jdbcTemplate.queryForObject(FIND_FILE_BY_ID, rowMapper(), fileId);
}

private RowMapper<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 — jdbcTemplateжана аркылуу id боюнча издөө RowMapper. 8 - 15 - RowMapperбиздин конкреттүү иш үчүн ишке ашыруу, маалыматтар базасын жана моделдик талааларды салыштыруу үчүн. Келиңиз, FileManagerфайлыбыз кантип жүктөлгөнүн карап көрөлү:
public Resource download(String key) throws IOException {
   Path path = Paths.get(DIRECTORY_PATH + key);
   Resource resource = new UrlResource(path.toUri());
   if (resource.exists() || resource.isReadable()) {
       return resource;
   } else {
       throw new IOException();
   }
}
Биз файлды an object катары кайтарабыз Resourceжана биз ачкыч менен издейбиз. 3 - Resourceжол + ачкычын бойлото түзүңүз. 4 - 8 — берилген жолдогу файл бош эмес экенин текшерип, аны окуйбуз. Эгер баары ойдогудай болсо, биз аны кайтарабыз, ал эми жок болсо, IOException жогору жагына ыргытабыз. Почтачыдагы ыкмабызды текшерип көрөлү: Файлдарды тиркемеге жана алар жөнүндө маалыматтарды маалымат базасына сактоо - 7Көрүнүп тургандай, ал жакшы иштеди))

Жок кылуу

@DeleteMapping(value = "/{id}")
public ResponseEntity<Void> delete(@PathVariable("id") Long id) {
   try {
       fileService.delete(id);
       return new ResponseEntity<>(HttpStatus.OK);
   } catch (IOException e) {
       return new ResponseEntity<>(HttpStatus.NOT_FOUND);
   }
}
Бул жерде өзгөчө эч нерсе жок: 404 колдонбой калса, биз да кайтарабыз try-catch. Кызмат интерфейси:
void delete(Long fileId) throws IOException;
Ишке ашыруу:
@Transactional(rollbackFor = {IOException.class})
@Override
public void delete(Long fileId) throws IOException {
   FileInfo file = fileDAO.findById(fileId);
   fileDAO.delete(fileId);
   fileManager.delete(file.getKey());
}
1 - ошондой эле IOException пайда болгондо берorштердин өзгөртүүлөрүнүн (жок кылынышынын) артка кайтарылышы. 5 - маалымат базасынан файл жөнүндө маалыматты жок кылуу. 6 - файлдын өзүн биздин “сактоодон” жок кылыңыз. дао интерфейси:
void delete(Long fileId);
Ишке ашыруу:
private static final String DELETE_FILE_BY_ID = "DELETE FROM files_info WHERE id = ?";

@Override
public void delete(Long fileId) {
   jdbcTemplate.update(DELETE_FILE_BY_ID, fileId);
}
Мындай эч нерсе жок - жөн гана жок кылыңыз. Файлдын өзүн жок кылуу:
public void delete(String key) throws IOException {
       Path path = Paths.get(DIRECTORY_PATH + key);
       Files.delete(path);
   }
}
Почтачыны колдонобуз: Файлдарды тиркемеге жана алар жөнүндө маалыматтарды маалымат базасына сактоо - 8Сактагычты карайбыз: Файлдарды тиркемеге жана алар жөнүндө маалыматтарды маалымат базасына сактоо - 9Бош :) Азыр базада: Сохранение файлов в приложение и данных о них на БД - 10Баары жакшы экенин көрүп жатабыз))

Сыноо

Келгиле, биздин тест жазууга аракет кылалы FileManager. Биринчиден, сыноо бөлүгүнүн түзүмүн карап көрөлү: mockFile.txt бул файл, анын жардамы менен биз файлдарды сактоо менен операцияларыбызды текшеребиз. testFileStorage биздин сактагычыбызды алмаштырат. FileManagerTest:
public class FileManagerTest {

   private static MultipartFile multipartFile;

   private static FileManager manager;

   private static FileInfo file;

   @BeforeClass
   public static void prepareTestData() throws IOException {
       file = FileInfo.builder()
               .id(9L)
               .name("mockFile.txt")
               .key("mockFile.txt")
               .size(38975L)
               .uploadDate(LocalDate.now())
               .build();
       multipartFile = new MockMultipartFile("mockFile", "mockFile.txt", "txt",
               new FileInputStream("src/test/resources/mockFile.txt"));
       manager = new FileManager();
   }
Бул жерде биз тесттик дайындоону көрөбүз. Файлды сактоо тести:
@Test
public void uploadTest() throws IOException {
   ReflectionTestUtils.setField(manager, "DIRECTORY_PATH", "src/test/resources/testFileStorage/");

   manager.upload(multipartFile.getBytes(), "mockFile.txt");

   Path checkFile = Paths.get("src/test/resources/testFileStorage/mockFile.txt");
   assertThat(Files.exists(checkFile)).isTrue();
   assertThat(Files.isRegularFile(checkFile)).isTrue();
   assertThat(Files.size(checkFile)).isEqualTo(multipartFile.getSize());
   Files.delete(checkFile);
}
3 — тесттик чагылдырууну колдонуп, биз файлды сактоо жолун коюу үчүн кызматтагы константабызды өзгөртөбүз. 5 — текшерorп жаткан ыкманы чакырыңыз. 7 - 10 — сактоонун туура аткарылышын текшеребиз. 11 - сакталган файлды жок кылуу (биз эч кандай из калтырбашыбыз керек).
@Test
public void downloadTest() throws IOException {
   ReflectionTestUtils.setField(manager, "DIRECTORY_PATH", "src/test/resources/");

   Resource resource = manager.download(file.getKey());

   assertThat(resource.isFile()).isTrue();
   assertThat(resource.getFilename()).isEqualTo(file.getName());
   assertThat(resource.exists()).isTrue();
}
Файлды жүктөө сынагы: 3 - дагы, биздин FileManager. 5 - текшерorп жаткан ыкманы колдонуу. 7 - 9 — аткарылышынын жыйынтыгын текшерүү. Файлды жок кылуу тести:
@Test
public void deleteTest() throws IOException {
   Path checkFile = Paths.get("src/test/resources/testFileStorage/mockFile.txt");
   Files.createFile(checkFile);
   assertThat(Files.exists(checkFile)).isTrue();
   assertThat(Files.isRegularFile(checkFile)).isTrue();
   ReflectionTestUtils.setField(manager, "DIRECTORY_PATH", "src/test/resources/testFileStorage/");

   manager.delete(file.getKey());

   assertThat(Files.notExists(checkFile)).isTrue();
}
9 - 3 - 4 - жолду коюп, файлды түзүңүз. 5 - 6 - биз анын бар экенин текшеребиз. 9 - биз текшерилүүчү ыкманы колдонобуз. 77 - an objectтин жок экенин текшеребиз. Келгиле, көз карандылык боюнча бизде эмне бар экенин карап көрөлү:
<dependencies>
   <dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-data-jdbc</artifactId>
   </dependency>
   <dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-web</artifactId>
   </dependency>
   <dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-test</artifactId>
       <scope>test</scope>
       <exclusions>
           <exclusion>
               <groupId>org.junit.vintage</groupId>
               <artifactId>junit-vintage-engine</artifactId>
           </exclusion>
       </exclusions>
   </dependency>
   <dependency>
       <groupId>org.projectlombok</groupId>
       <artifactId>lombok</artifactId>
       <version>1.18.10</version>
       <scope>provided</scope>
   </dependency>
   <dependency>
       <groupId>mysql</groupId>
       <artifactId>mysql-connector-java</artifactId>
       <version>8.0.18</version>
   </dependency>
   <dependency>
       <groupId>commons-codec</groupId>
       <artifactId>commons-codec</artifactId>
       <version>1.9</version>
   </dependency>
   <dependency>
       <groupId>junit</groupId>
       <artifactId>junit</artifactId>
       <version>4.13-rc-2</version>
       <scope>test</scope>
   </dependency>
</dependencies>
Бүгүн мен үчүн баары ушул))
Комментарийлер
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION