4차산업혁명의 일꾼/Java&Spring웹개발과 서버 컴퓨터

java 파일 다운로드/ 엑셀및 pdf 그리고 소켓통신 그리고 JSON 복습

르무엘 2024. 6. 17. 19:25
package com.example.demo.controller;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.InputStreamResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;

@Controller
public class FileDownloadController {

    @Value("${file.upload-dir}")
    private String uploadDir;

    @GetMapping("/download")
    public ResponseEntity<InputStreamResource> downloadFile(@RequestParam String fileName) throws IOException {
        // 파일 경로
        String filePath = uploadDir + fileName;
        File file = new File(filePath);

        if (!file.exists()) {
            return ResponseEntity.status(HttpStatus.NOT_FOUND).body(null);
        }

        // 파일을 InputStreamResource로 감싸기
        InputStreamResource resource = new InputStreamResource(new FileInputStream(file));

        // 파일의 MIME 타입 결정 (기본적으로 바이너리로 설정)
        String contentType = "application/octet-stream";
        try {
            contentType = MediaType.APPLICATION_OCTET_STREAM_VALUE;
        } catch (Exception ex) {
            ex.printStackTrace();
        }

        return ResponseEntity.ok()
                .header(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + file.getName())
                .contentType(MediaType.parseMediaType(contentType))
                .contentLength(file.length())
                .body(resource);
    }
}

자바로 ResponseEntity 는 RESTful한 것을 작성하고 위해 사용하는데 객체와 응답코드를 같이 보내줄수 있다.

 

타임리프와 스프링 커스텀태그가 유사한 부분이 있는데 뭐 어쨌건 이미 잘 짜여진 타임리프를 쓰는게 나은 이유인것 같다. 유지보수 측면이랑 디버깅 측면을 고려하면 말이다.

 

수타게 보아온 엑셀은 poi 패키지 가져와서 Workbook , Sheet 클래스를 중점으로 구성하여

ByteArrayOutputStream 을 통해 보내면 되는 거를 새록새록 떠올려본다.

package com.example.demo.controller;

import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

import java.io.ByteArrayOutputStream;
import java.io.IOException;

@Controller
public class ExcelController {

    @GetMapping("/download/excel")
    public ResponseEntity<byte[]> downloadExcel() throws IOException {
        Workbook workbook = new XSSFWorkbook();
        Sheet sheet = workbook.createSheet("Sheet1");

        // 헤더 행 생성
        Row headerRow = sheet.createRow(0);
        Cell headerCell1 = headerRow.createCell(0);
        headerCell1.setCellValue("ID");
        Cell headerCell2 = headerRow.createCell(1);
        headerCell2.setCellValue("Name");

        // 데이터 행 생성
        Row dataRow = sheet.createRow(1);
        Cell dataCell1 = dataRow.createCell(0);
        dataCell1.setCellValue(1);
        Cell dataCell2 = dataRow.createCell(1);
        dataCell2.setCellValue("John Doe");

        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        workbook.write(outputStream);
        workbook.close();

        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
        headers.setContentDispositionFormData("attachment", "sample.xlsx");

        return ResponseEntity
                .ok()
                .headers(headers)
                .body(outputStream.toByteArray());
    }
}

 

pdf 는 iText 가져와서 똑같이 ByteArrayOutputStream 을 통해 보내면 되는 것 또한 새록새록이다.

PdfWriter/ Document 냐 WorkBook/Sheet이냐 차이가 좀 있지만 말이다.

package com.example.demo.controller;

import com.itextpdf.kernel.pdf.PdfWriter;
import com.itextpdf.layout.Document;
import com.itextpdf.layout.element.Paragraph;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

import java.io.ByteArrayOutputStream;
import java.io.IOException;

@Controller
public class PdfController {

    @GetMapping("/download/pdf")
    public ResponseEntity<byte[]> downloadPdf() throws IOException {
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        PdfWriter writer = new PdfWriter(outputStream);
        Document document = new Document(new com.itextpdf.kernel.pdf.PdfDocument(writer));

        document.add(new Paragraph("Hello, PDF!"));
        document.close();

        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_PDF);
        headers.setContentDispositionFormData("attachment", "sample.pdf");

        return ResponseEntity
                .ok()
                .headers(headers)
                .body(outputStream.toByteArray());
    }
}

 

http통신에만 익숙해 왔는데 socket 양방향 통신도 다시 한번 살펴본다.

websockt가 config 설정을 하고

메시지 브로커와 목적지 prefixes를 하고나서

endpoint 설정을 하면 된다.

import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

    @Override
    public void configureMessageBroker(MessageBrokerRegistry config) {
        config.enableSimpleBroker("/topic"); // 메시지 브로커 설정
        config.setApplicationDestinationPrefixes("/app"); // 애플리케이션 목적지 프리픽스 설정
    }

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/ws").withSockJS(); // 웹소켓 엔드포인트 설정
    }
}

 

이것을 다루기 위해서 메시지 핸들러가 필요한데, 클라이언트로부터 메세지를 받고, 브로커를 통해 다른 클라이언트에 메시지를 전달하는 역할을 한다. 양방향 통신이 되는 순간이다.

import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.stereotype.Controller;

@Controller
public class ChatController {

    @MessageMapping("/hello")
    @SendTo("/topic/greetings")
    public Greeting greeting(HelloMessage message) throws Exception {
        Thread.sleep(1000); // simulated delay
        return new Greeting("Hello, " + message.getName() + "!");
    }
}

 

프론트에서 이렇게 소켓 만들어서 위에 설정한 ws 엔드포인트로 보낼 준비하고, @MessageMapping(app/hello) 어노테이션을 통한 메세지 핸들러로 전달하면 브로커가 (/topic)  @SendTo 어노테이션을 통해(/grettings)다른 클라이언트에게 전달한다.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>WebSocket Chat</title>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/sockjs-client/1.1.4/sockjs.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/stomp.js/2.3.3/stomp.min.js"></script>
    <script>
        var stompClient = null;

        function connect() {
            var socket = new SockJS('/ws');
            stompClient = Stomp.over(socket);
            stompClient.connect({}, function (frame) {
                console.log('Connected: ' + frame);
                stompClient.subscribe('/topic/greetings', function (greeting) {
                    showGreeting(JSON.parse(greeting.body).content);
                });
            });
        }

        function disconnect() {
            if (stompClient !== null) {
                stompClient.disconnect();
            }
            console.log("Disconnected");
        }

        function sendName() {
            var name = document.getElementById('name').value;
            stompClient.send("/app/hello", {}, JSON.stringify({'name': name}));
        }

        function showGreeting(message) {
            var response = document.getElementById('response');
            var p = document.createElement('p');
            p.appendChild(document.createTextNode(message));
            response.appendChild(p);
        }
    </script>
</head>
<body>
    <div>
        <input type="text" id="name" placeholder="Enter your name" />
        <button onclick="sendName()">Send</button>
        <button onclick="connect()">Connect</button>
        <button onclick="disconnect()">Disconnect</button>
    </div>
    <div id="response"></div>
</body>
</html>

 

어쨌건 또 중요포인트로 JSON은 parse 해서 객체로 만들고 stringify로 문자열로 만들고

{
    "name": "John",
    "age": 30,
    "isStudent": false,
    "address": {
        "street": "123 Main St",
        "city": "Anytown"
    },
    "hobbies": ["reading", "traveling", "swimming"]
}

날자 타입이 지원안되서 그렇지 상당히 자주 사용되고 유용한 데이터 타입이다. 배열[] 과 함께 쓸수 있어서 더좋다.

LIST