اچو ته تصور ڪريو ته توهان پنهنجي ويب ايپليڪيشن تي ڪم ڪري رهيا آهيو. منهنجي مضمونن ۾ آئون هن موزاڪ جي انفرادي ٽڪرن کي ڏسان ٿو، جهڙوڪ:
- 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
. اهو بائيٽ جي هڪ صف جي طور تي پڻ حاصل ڪري سگهجي ٿو، پر مون کي هي اختيار بهتر آهي، ڇاڪاڻ ته اسان 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 — اسان ڊيٽابيس ۾ محفوظ ڪرڻ لاءِ هڪ ادارو ٺاهيندا آهيون. 17 — اسان اداري کي معلومات سان گڏ ڊيٽابيس ۾ هلائيندا آهيون. 18 - هيش ٿيل نالي سان فائل محفوظ ڪريو. 20 — اسان ٺاهيل ادارو واپس ڪريون ٿا FileInfo
، پر ڊيٽابيس ۾ ٺاهيل id سان (هي هيٺ بحث ڪيو ويندو) ۽ ٺاھڻ جي تاريخ. ھڪڙي فائل کي چاٻي پيدا ڪرڻ جو طريقو:
private String generateKey(String name) {
return DigestUtils.md5Hex(name + LocalDateTime.now().toString());
}
هتي اسان نالو + ٺاھڻ جي تاريخ کي هٽايو، جيڪو اسان جي انفراديت کي يقيني بڻائيندو. Dao پرت انٽرفيس:
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 - اسان انٽيٽي کي محفوظ ڪريون ٿا، پر وڌيڪ پيچيده انداز ۾، اعتراض جي واضح ٺاھڻ سان، PreparedStatement
جيئن پيدا ٿيل آئي ڊي کي ڪڍي سگھجي ٿو (اھو ان کي ڌار ڌار درخواست سان نه، پر ردعمل ميٽا ڊيٽا جي صورت ۾ ڪڍي ٿو. ). 22 - 26 - اسان پنهنجي ڊگهي تڪليف واري اداري جي تعمير کي مڪمل ڪريون ٿا ۽ ان کي مٿي ڏيون ٿا (حقيقت ۾، هو ان کي مڪمل نه ڪندو آهي، پر هڪ نئين شئي ٺاهي، منتقل ٿيل فيلڊ کي ڀرڻ ۽ باقي اصل کان نقل ڪري ٿو) . اچو ته ڏسون ته اسان جون فائلون ڪيئن محفوظ ٿينديون 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 - اسان فائل قبول ڪريون ٿا بائٽس جي هڪ صف طور ۽ الڳ الڳ نالو جنهن جي تحت ان کي محفوظ ڪيو ويندو (اسان جي ٺاهيل ڪيئي). 2 - 3 - ھڪڙو رستو ٺاھيو (۽ رستي ۾ اسين رستو لکون ٿا ۽ اسان جي ڪيئي) ۽ ان سان گڏ ھڪڙي فائل. 6 - 7 - ھڪڙو وهڪرو ٺاھيو ۽ اتي اسان جا بائيٽ لکو (۽ اھو سمورو سامان ان ۾ ويڙھيو try-finally
ته پڪ ڪريو ته وهڪرو ضرور بند ٿيندو). بهرحال، ڪيترائي طريقا IOException اڇلائي سگھن ٿا. انهي صورت ۾، طريقي جي هيڊر ۾ بيان ڪيل اڳتي وڌڻ جي مهرباني، اسان ان کي ڪنٽرولر ڏانهن منتقل ڪنداسين ۽ 400 جي حيثيت ڏينداسين. اچو ته پوسٽمن ۾ سڄي شيء کي جانچيو: جيئن توهان ڏسي سگهو ٿا، سڀ ڪجهه ٺيڪ آهي، جواب 201 آهي، جواب JSON ڊيٽابيس ۾ اسان جي مسلسل اداري جي صورت ۾ آيو، ۽ جيڪڏهن اسان اسان جي اسٽوريج ۾ ڏسون ٿا: ڊي بي: اسان ڏسون ٿا ته اسان وٽ هڪ نئون قدر آهي. (=*؛*=)
ڊائون لوڊ ڪريو
ڪنٽرولر:@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 ادارو ڪڍيو. 5 - اداري مان ڪي استعمال ڪندي، فائل ڊائون لوڊ ڪريو 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 ذريعي ڪنهن اداري کي ڳولڻ جو طريقو ۽ فائل ڊائون لوڊ ڪرڻ، سواء شايد 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();
}
}
اسان فائل کي هڪ اعتراض جي طور تي واپس ڪريون ٿا Resource
، ۽ اسان کي ڳوليندا سين. 3 - Resource
رستي سان ٺاهيو + ڪيچ. 4 - 8 - اسان چيڪ ڪريون ٿا ته ڏنل رستي تي فائل خالي نه آهي ۽ ان کي پڙهو. جيڪڏهن سڀ ڪجهه ٺيڪ آهي، اسان ان کي واپس ڪريون ٿا، ۽ جيڪڏهن نه، اسان مٿي تي IOException اڇليندا آهيون. اچو ته پوسٽمن ۾ اسان جو طريقو چيڪ ڪريو: جيئن اسان ڏسي سگهون ٿا، اهو ڪم ڪيو ٺيڪ))
حذف ڪريو
@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);
}
}
اسان پوسٽمن استعمال ڪندا آهيون: اسان اسٽوريج ۾ ڏسون ٿا: خالي :) هاڻي ڊيٽابيس ۾: اسان ڏسون ٿا ته سڀ ڪجهه سٺو آهي))
ٽيسٽ
اچو ته اسان جي لاء هڪ امتحان لکڻ جي ڪوشش ڪريو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 - اسان چيڪ ڪريون ٿا ته اعتراض هاڻي اتي ناهي. ۽ اچو ته ڏسون ته اسان وٽ انحصار جي لحاظ کان ڇا آهي:
<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