آئیے تصور کریں کہ آپ اپنی ویب ایپلیکیشن پر کام کر رہے ہیں۔ میں اپنے مضامین میں اس موزیک کے انفرادی ٹکڑوں کو دیکھتا ہوں، جیسے:
- 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
، لیکن ڈیٹا بیس میں تیار کردہ آئی ڈی کے ساتھ (اس پر ذیل میں بات کی جائے گی) اور تخلیق کی تاریخ۔ فائل کی کلید بنانے کا طریقہ:
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 - ہم ہستی کو محفوظ کرتے ہیں، لیکن زیادہ پیچیدہ انداز میں، آبجیکٹ کی واضح تخلیق کے ساتھ 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 ڈیٹا بیس میں ہماری مستقل ہستی کی شکل میں آیا، اور اگر ہم اپنے اسٹوریج کو دیکھیں: 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 - ڈیٹا بیس سے ملحقہ فائل انفو ہستی کو نکالیں۔ 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);
}
یہاں کچھ خاص طور پر دلچسپ نہیں ہے: آئی ڈی کے ذریعہ کسی ہستی کو تلاش کرنے اور فائل کو ڈاؤن لوڈ کرنے کا طریقہ، سوائے شاید 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 - فائل کو ہمارے "اسٹوریج" سے ہی ڈیلیٹ کر دیں۔ داو انٹرفیس:
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