Slom! Moj pripravnik je slike sačuvao direktno na serveru!
Slom! Moj pripravnik je slike sačuvao direktno na serveru!
Mali Petar je novi pripravnik; kao tech lead, dao sam mu veoma jednostavan zadatak za vežbu — da sačuva slike koje se šalju iz frontend markdown editora na serversku stranu. Ali on je te slike zaista sačuvao direktno na serveru, čime me je prilično iznervirao — zar nije mogao da upotrebi neki object storage servis, recimo OSS ili MinIO?
On je bez trunke griže savesti uzvratio: „Ko ti je kriv što nisi objasnio jasno, otići ću kod šefa da te otpusti!" Ja sam istog trenutka popustio i rekao: „Hajde, hajde, da ću ti od početka da pokažem kako da sačuvaš sliku na OSS, važi?"
„Nema potrebe, bolje ću ja tebe da naučim." Mali Petar je bio veoma siguran u sebe. U nastavku su njegove beleške o integraciji OSS-a u Spring Boot aplikaciju.
Zvanična izjava: nadam se da će se voditelj proizvoda Aliyun OSS-a, pošto ovo pročita, javiti da mi namiri troškove promocije (šala). Iskreno — Spring Boot + OSS je u prilično čest u stvarnom razvoju.
1. Otvaranje OSS-a
OSS je skraćenica od Object Storage Service; to je object storage servis koji pruža Aliyun. Konkurenti u Kini uključuju Qiniu Cloud Kodo i Tencent Cloud COS.
Prvi korak, prijavite se na zvanični sajt Aliyun-a, pretražite ključnu reč „OSS" i uđite na stranicu OSS proizvoda.
Drugi korak, ako ste novi OSS korisnik, možete iskoristiti promotivnu cenu za nove korisnike koja važi 6 meseci — mada će pri obnovi ipak malo zaboleti.
Treći korak, uđite u OSS menadžment konsolu, kliknite na „Bucket list", zatim na „Create Bucket".

Reč bucket bukvalno znači kanta; ovde označava storage prostor, odnosno kontejner za čuvanje objekata. Obratite pažnju da read/write dozvolu postavite na „public read", što znači da korisnicima interneta dozvoljavate pristup slikama u cloud prostoru.
Četvrti korak, kliknite na „OK" i time je otvaranje uspešno završeno.
2. Integracija OSS-a
Prvi korak, u pom.xml datoteku dodajte OSS zavisnost.
<!-- Aliyun OSS -->
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>3.10.2</version>
</dependency>Drugi korak, u application.yml datoteku dodajte OSS konfiguracione stavke.
aliyun:
oss:
# javni domen za pristup OSS servisu
endpoint: oss-cn-beijing.aliyuncs.com
# identifikator korisnika korišćen u autentifikaciji pristupa
accessKeyId: LTAI5
# tajni ključ koji korisnik koristi za potpisivanje i koji OSS koristi za proveru potpisa
accessKeySecret: RYN
# storage prostor OSS-a
bucketName: javasi-oss1
# maksimalna veličina datoteke za otpremanje (MB)
maxSize: 3
# prefiks putanje direktorijuma za otpremanje
dir:
prefix: codingmore/images/Treći korak, dodajte novu konfiguracionu klasu OssClientConfig.java; uglavnom služi da pomoću @Value anotacije preuzme konfiguracione stavke iz konfiguracione datoteke, a zatim kreira OSSClient.
@Configuration
public class OssClientConfig {
@Value("${aliyun.oss.endpoint}")
String endpoint ;
@Value("${aliyun.oss.accessKeyId}")
String accessKeyId ;
@Value("${aliyun.oss.accessKeySecret}")
String accessKeySecret;
@Bean
public OSSClient createOssClient() {
return (OSSClient)new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
}
}Četvrti korak, dodajte novi interfejs za otpremanje datoteka OssController.java, sa parametrom MultipartFile.
@Controller
@Api(tags = "Otpremanje")
@RequestMapping("/ossController")
public class OssController {
@Autowired
private IOssService ossService;
@RequestMapping(value = "/upload",method=RequestMethod.POST)
@ResponseBody
@ApiOperation("otpremanje")
public ResultObject<String> upload(@RequestParam("file") MultipartFile file, HttpServletRequest req) {
return ResultObject.success(ossService.upload(file));
}
}Peti korak, dodajte novi Service koji otprema datoteku na OSS i vraća putanju čuvanja datoteke.
@Service
public class OssServiceImpl implements IOssService{
@Value("${aliyun.oss.maxSize}")
private int maxSize;
@Value("${aliyun.oss.bucketName}")
private String bucketName;
@Value("${aliyun.oss.dir.prefix}")
private String dirPrefix;
@Autowired
private OSSClient ossClient;
@Override
public String upload(MultipartFile file) {
try {
return upload(file.getInputStream(), file.getOriginalFilename());
} catch (IOException e) {
LOGGER.error(e.getMessage());
}
return null;
}
@Override
public String upload(InputStream inputStream,String name) {
String objectName = getBucketName(name);
// Kreira PutObject zahtev.
ossClient.putObject(bucketName, objectName, inputStream);
return formatPath(objectName);
}
private String getBucketName(String url){
String ext = "";
for(String extItem:imageExtension){
if(url.indexOf(extItem) != -1){
ext = extItem;
break;
}
}
return dirPrefix+ DateUtil.today()+"/"+ IdUtil.randomUUID()+ext;
}
private String formatPath(String objectName){
return "https://" +bucketName+"."+ ossClient.getEndpoint().getHost() + "/" + objectName;
}
}Šesti korak, otvorite Apipost i testirajte OSS interfejs za otpremanje; obratite pažnju da za parametar izaberete tip file, a nakon slanja ćete videti link slike koji vraća serverska strana.

Sedmi korak, uđite u pozadinsku administraciju Aliyun OSS-a i možete potvrditi da je slika zaista uspešno otpremljena.

3. Preuzimanje frontend koda za testiranje OSS interfejsa za otpremanje
codingmore-admin-web je frontend menadžment projekat Codingmore-a; možete ga preuzeti lokalno preko sledeće adrese.
https://github.com/itwanger/codingmore-admin-web
Nakon izvršavanja komande yarn run dev pokrenuće se web admin panel; uđite u stranicu za uređivanje članaka, izaberite jednu sliku za otpremanje i možete potvrditi da slika može normalno da se otpremi sa frontend-a na serversku stranu, a zatim sa serverske strane na OSS, nakon čega se frontend-u vraća link za pristup slici.

4. Automatska konverzija linkova pomoću OSS-a
Prvi korak, u PostsServiceImpl.java dodajte metodu za konverziju linkova slika; uglavnom koristi regularne izraze da pronađe spoljašnje linkove u sadržaju članka, a zatim otprema slike sa spoljašnjih linkova na OSS i zamenjuje originalne slike sa spoljašnjih linkova.
// Markdown sintaksa za poklapanje slika
public static final String IMG_PATTERN = "\\!\\[.*\\]\\((.*)\\)";
private void handleContentImg(Posts posts) {
String content = posts.getPostContent();
Pattern p = Pattern.compile(IMG_PATTERN, Pattern.CASE_INSENSITIVE);
Matcher m = p.matcher(content);
Map<String, Future<String>> map = new HashMap<>();
while (m.find()) {
String imageTag = m.group();
LOGGER.info("Zamenjujem koristeći grupu{}", imageTag);
String imageUrl = imageTag.substring(imageTag.indexOf("(") + 1, imageTag.indexOf(")"));
// Potvrđuje da je link sa ovog sajta, preskače obradu
if (imageUrl.indexOf(iOssService.getEndPoint()) != -1) {
continue;
}
// Otprema sliku na OSS putem thread pool-a
Future<String> future = ossUploadImageExecutor.submit(() -> {
return iOssService.upload(imageUrl);
});
map.put(imageUrl, future);
}
for (String oldUrl : map.keySet()) {
Future<String> future = map.get(oldUrl);
try {
String imageUrl = future.get();
content = content.replace(oldUrl, imageUrl);
} catch (InterruptedException | ExecutionException e) {
LOGGER.error("Greška pri preuzimanju linka slike{}", e.getMessage());
}
}
posts.setPostContent(content);
}Drugi korak, u OssServiceImpl.java dodajte metodu koja otprema sliku na OSS na osnovu adrese spoljašnjeg linka.
public String upload(String url) {
String objectName = getFileName(url);
try (InputStream inputStream = new URL(url).openStream()) {
ossClient.putObject(bucketName, objectName, inputStream);
} catch (IOException e) {
LOGGER.error(e.getMessage());
}
return formatOSSPath(objectName);
}Treći korak, putem web admin panela testirajte da li je konverzija spoljašnjih linkova uspela. Prvo nabavite dve slike sa spoljašnjih linkova — videćete da ih markdown u pregledu ne prikazuje.

Zatim kliknemo na objavu i videćemo da se obe slike normalno prikazuju, jer su prebačene na OSS adresu za pristup slici.

5. Kratak rezime
Sve u svemu, kod kojim je pripravnik Mali Petar integrisao OSS u Spring Boot prilično je pouzdan. Možda je OSS + CDN najbolje rešenje za image hosting; međutim, problem da Aliyun HTTPS CDN na GitHub-u ne može da povuče izvor, zbog čega se slike ne prikazuju, i dalje nije efikasno rešen.
Za izvorni kod, možete ga direktno preuzeti sa putanje izvornog koda Codingmore🐱:
