Биёед тасаввур кунем, ки шумо дар болои барномаи веби худ кор карда истодаед. Дар мақолаҳои худ ман ба қисмҳои алоҳидаи ин мозаика назар мекунам, масалан:
- Санҷиши ҳамгироии пойгоҳи додаҳо бо истифода аз MariaDB барои иваз кардани MySql
- Татбиқи замимаи бисёрзабонӣ
@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 — мо an objectро бо маълумот ба базаи маълумот меронем. 18 - файлро бо номи ҳашшуда захира кунед. 20 — мо an objectи сохташударо бармегардонем FileInfo
, аммо бо идентификатсияи тавлидшуда дар базаи маълумот (ин дар поён баррасӣ хоҳад шуд) ва санаи офариниш. Усули тавлиди калиди файл:
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
то id-и тавлидшуда берун карда шавад (он онро на бо дархости алоҳида, балки дар шакли метамаълумоти посух берун мекунад ). 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 медиҳем. Биёед ҳама чизро дар Postman санҷем: Тавре ки шумо мебинед, ҳама чиз хуб аст, ҷавоб 201 аст, ҷавоби JSON дар шакли як сохтори устувори мо дар пойгоҳи додаҳо омадааст ва агар мо ба анбори худ назар андозем: DB: мо мебинем, ки мо арзиши нав дорем. (=*;*=)
Зеркашӣ кунед
Назоратчӣ:@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 - an objectи ҳамсояи FileInfo аз пойгоҳи додаҳо берун кунед. 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);
}
Дар ин ҷо ҳеҷ чизи ҷолибе вуҷуд надорад: усули ҷустуҷӯи an object аз рӯи ID ва зеркашии файл, ба истиснои 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 — ҷустуҷӯ аз рӯи ID бо истифода jdbcTemplate
аз ва 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-ро ба боло мепартоем. Биёед усули худро дар Postman тафтиш кунем: Тавре ки шумо мебинед, он хуб кор кард))
Нобуд кунед
@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. 5 - нест кардани маълумот дар бораи файл аз пойгоҳи додаҳо. 6 - худи файлро аз "захираи" мо нест кунед. интерфейси dao:
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);
}
}
Мо Postman-ро истифода мебарем: Мо дар анбор нигоҳ мекунем: Холӣ :) Ҳоло дар база: Мо мебинем, ки ҳама чиз хуб аст))
Санҷиш
Биёед кӯшиш кунем, ки барои мо тест нависем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 — усули озмудашавандаро даъват кунед. 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 - усули санҷидашударо истифода баред. 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>
Ин ҳама барои ман имрӯз аст))
GO TO FULL VERSION