개발/Java

☕️Java:: 바코드 출력 API 제작하기

hyuunii 2023. 8. 4. 16:04

상황

환자 등록번호를 스캔했을때 그 값으로 바로 바코드 출력하는 API가 필요했다.
 
 

고민

1. 백+프론트 함께 작업하는 방식과 프론트에서만 작업하는 방식 둘 모두 가능해보였고, 사실 후자가 훨씬 더 간단해보였다.
백이 들어가면 당연히 S3 등 서버에 저장하는 리소스도 필요하고 매번 그걸 불러와야 한다고 생각했기 때문이다.
 
2. 만약 백이 들어간다고 하더라도 두 가지 방법이 또 생각이 났다.
1) 환자 등록번호 전체를 미리 바코드를 출력해서 S3에 저장해놓고 API 출력이 들어오면 뷰에 뿌려주기만 하기
2) API 출력이 들어올때마다 바코드를 새로 생성해서 S3에 저장+불러오기를 동시에 진행하기
 
3. 위의 1)번이든 2)번이든 문제가 많았다.
1)은 당연히.. 그 많은걸 언제 다 하냐는 거였고
2)는 동일한 환자의 바코드 역시 매번 새로 생성해서 S3에 저장한다는건데... 사실 그렇다면 스토리지에 쌓아둘 필요가 없는 정보들이라 주기적으로 스토리지를 비워주는게 여러모로 나을 것 같았는데, 역시 너무 비효율적이어보였다.
 
그래서 일단 구글링을 계속 시도해봤는데,
Barbecue라는 자바의 바코드 생성 라이브러리가 있었다.
 
해당 라이브러리를 쓰면 바로 바코드 이미지를 다운받을수도 있지만,
저장하는 단계 없이 Base64로 인코딩된 이미지 url을 뽑아낼수도 있었다!!!!
 
뽑힌 url은 로컬 뿐만 아니라 어느데서나 링크를 붙여넣기만 하면 바로 이미지가 뜨는 url이라 정말 혁신적이었다ㅋㅋㅋㅋ
그래서 해당 방법대로 API를 제작했다.
 
 
아, 참고로 아래 코드의 바코드는 Code 39 Standard 유형이다.
 
 
 

코딩 과정

1) jar 파일 다운로드

아래 공홈에서 받아도 되고
Barbecue - Java barcode generator 
https://sourceforge.net/projects/barbecue/
 
아래 첨부파일 통해 받아도 된다. 오픈소스기때문에 파일을 붙여놓았다.

barbecue-1.5-beta1
0.09MB

 
 
 
 
 
2) 인텔리제이에서 프로젝트에 라이브러리 추가하기
file - project structure를 열고 modules-Dependencies에서 방금 받은 jar 파일을 + 누르고 추가해준다.
 
 
 
 
 
3) 메이븐 기준 설정파일 pom.xml에 추가하기
사실 난 위의 방법으로 외부 라이브러리인 🍖바베큐를 잘 읽어들이지 못했다ㅎㅎ,,,
그래서 걍 폼파일에 추가해주고 빌드했다.

<!-- https://mvnrepository.com/artifact/net.sourceforge.barbecue/barbecue -->
		<dependency>
			<groupId>net.sourceforge.barbecue</groupId>
			<artifactId>barbecue</artifactId>
			<version>1.5-beta1</version>
		</dependency>

 
 
 
 
 
4) 코드 작성
두 가지의 코드를 올린다.
하나는 바로 로컬로 다운로드되는 코드 & 하나는 위에서 언급했듯 이미지의 url이 뽑히는 코드다.
 

 
 
 
Controller
아래 컨트롤러는 url이 리턴된다.
바로 다운할 경우는 리턴값이 필요가 없어 void로만 비꿔주면 된다.

@RestController
@AllArgsConstructor
public class ProgramController {
    private final ProgramService programService;

    @GetMapping("/barcode")
    public String getBarcode(@RequestParam("patient_id") String patientId) {
        return programService.getBarcode(patientId);
    }
}

보면 알겠지만 환자 등록번호(patient_id)가 파람으로 들어온다.
 
 
 
 
 
Service
아래의 코드는 API 호출시 PNG 확장자 이미지가 현재 작업중인 프로젝트 폴더 내에 바로 다운로드된다.
리턴값이 없으니 String만 void로 바꿔주자.

@Slf4j
@Service
@AllArgsConstructor
public class ProgramService {

    public String getBarcode(String patientId) {
        String str = patientId;
        String imgBase64 ="";
        try {
            Barcode barcode = BarcodeFactory.createCode128(str);
            File file = new File("barcode1.png");

            BarcodeImageHandler.savePNG(barcode, file);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

 
 
아래의 코드는 API 호출시 Base64로 인코딩된 바코드 이미지 링크가 return된다.

@Slf4j
@Service
@AllArgsConstructor
public class ProgramService {

    public String getBarcode(String patientId) {
        String str = patientId;
        String imgBase64 ="";
        try {
            Barcode39Image barcode = new Barcode39Image();
            imgBase64 = barcode.getBarcodeBase64(str, 2, 60);
            System.out.println("<img src=\"" + imgBase64 + "\" />"  );
        } catch (Exception e) {
            e.printStackTrace();
        }
        return imgBase64;
    }
}

 
 
 
Barcode39Image
코드가 길어서 닫아둔다.
열어보면 인코딩 안된 글자들이 마구 외계어처럼 나오는데 붙여넣어서 사용하는데는 아무 문제가 없다.

더보기
public class Barcode39Image{

    // FIX �대�吏� 湲곕낯 �볦씠(諛붿퐫�쒓린以� �ъ씠利덈줈 蹂��뺣릺湲곕븣臾몄뿉 1�댁긽�대㈃��)
    private static int IMAGE_WIDTH = 230;
    // FIX �대�吏� 湲곕낯 �믪씠(諛붿퐫�쒓린以� �ъ씠利덈줈 蹂��뺣릺湲곕븣臾몄뿉 1�댁긽�대㈃��)
    private static int IMAGE_HEIGHT = 70;

    // 諛붿퐫�� 湲곕낯 �볦씠
    private static int BARCODE_WIDTH = 2;
    // 諛붿퐫�� 湲곕낯 �믪씠
    private static int BARCODE_HEIGHT = 50;
    // 諛곌꼍�� RGB 以� R 媛�
    private static int COLOR_R = 255;
    // 諛곌꼍�� RGB 以� G 媛�
    private static int COLOR_G = 255;
    // 諛곌꼍�� RGB 以� B 媛�
    private static int COLOR_B = 255;
    // 諛곌꼍 �щ챸 泥섎━(遺덊닾紐� 0, �щ챸 1)
    private static Boolean TYPE_TRANS = false;
    // �대�吏� ����[BASE64 - png(�щ챸泥섎━媛���), jpeg & SAVE - png, jpeg , gif]
    private static String TYPE_IMAGE = "png";
    // ���� �꾩껜 寃쎈줈 + �뚯씪紐�
    private static String SAVE_FULL_PATH = "";


    /**
     諛붿퐫�� 媛꾨떒�� �⑥닔

     1.  �쇰꺼 吏��뺥븯湲�           - barcode.setLabel("cjbox.tistory.com");
     2.  �띿뒪�� �덈낫�닿쾶 �섍린 - barcode.setDrawingText(false);
     3.  �볦씠 議곗젅�섍린           - barcode.setBarWidth(2);  // 1~2 �곷떦
     4.  �믪씠 議곗젅�섍린           - barcode.setBarHeight(50); // 50 �곷떦
     5.  諛곌꼍�� 蹂�寃쏀븯湲�        - barcode.setBackground(Color.GREEN);
     6.  諛붿퐫�쒖깋 蹂�寃쏀븯湲�     - barcode.setForeground(Color.RED);

     */


    /**
     * <pre>
     * 諛붿퐫�� �대�吏� ����
     * </pre>
     *
     * @param barcodeText 諛붿퐫�� 蹂��섑븷 臾몄옄��
     * @param savePath    �대�吏� ���� �꾩튂
     * @throws BarcodeException
     * @throws OutputException
     */
    public static void saveBarcodeImage(String barcodeText, String savePath) throws BarcodeException, OutputException{
        TYPE_TRANS = false;
        SAVE_FULL_PATH = savePath + "/" + barcodeText + "."+ TYPE_IMAGE;

        createBarcodeImage(barcodeText);
    }

    /**
     * <pre>
     * 諛붿퐫�� �대�吏� ����
     * </pre>
     *
     * @param barcodeText 諛붿퐫�� 蹂��섑븷 臾몄옄��
     * @param savePath    �대�吏� ���� �꾩튂
     * @param typeImage   �대�吏� ����(png, jpeg , gif)
     * @throws BarcodeException
     * @throws OutputException
     */
    public static void saveBarcodeImage(String barcodeText, String savePath, String typeImage) throws BarcodeException, OutputException{
        TYPE_IMAGE = typeImage;
        TYPE_TRANS = false;
        SAVE_FULL_PATH = savePath + "/" + barcodeText + "."+ TYPE_IMAGE;

        createBarcodeImage(barcodeText);
    }

    /**
     * <pre>
     * 諛붿퐫�� �대�吏� ����
     * </pre>
     *
     * @param barcodeText 諛붿퐫�� 蹂��섑븷 臾몄옄��
     * @param savePath    �대�吏� ���� �꾩튂
     * @param transType   諛곌꼍 �щ챸(0-遺덊닾紐�,1-�щ챸)
     * @throws BarcodeException
     * @throws OutputException
     */
    public static void saveBarcodeImage(String barcodeText, String savePath, Boolean transType ) throws BarcodeException, OutputException{
        TYPE_TRANS = transType;

        SAVE_FULL_PATH = savePath + "/" + barcodeText + "."+ TYPE_IMAGE;

        createBarcodeImage(barcodeText);
    }


    /**
     * <pre>
     * 諛붿퐫�� �대�吏� ����
     * </pre>
     *
     * @param barcodeText 諛붿퐫�� 蹂��섑븷 臾몄옄��
     * @param savePath    �대�吏� ���� �꾩튂
     * @param boacodeWidth  諛붿퐫�� �볦씠
     * @param boacodeHeight 諛붿퐫�� �믪씠
     * @throws BarcodeException
     * @throws OutputException
     */
    public static void saveBarcodeImage(String barcodeText, String savePath, int boacodeWidth, int boacodeHeight ) throws BarcodeException, OutputException{
        TYPE_TRANS = false;
        BARCODE_WIDTH = boacodeWidth;
        BARCODE_HEIGHT = boacodeHeight;

        SAVE_FULL_PATH = savePath + "/" + barcodeText + "."+ TYPE_IMAGE;

        createBarcodeImage(barcodeText);
    }



    /**
     * <pre>
     * 諛붿퐫�� �대�吏� �앹꽦
     * </pre>
     *
     * @param barcodeText   諛붿퐫�� 蹂��섑븷 臾몄옄��
     * @throws BarcodeException
     * @throws OutputException
     */
    public static void createBarcodeImage(String barcodeText) throws BarcodeException, OutputException{

        Barcode barcode = null;
        BufferedImage image = null;
        Graphics2D g = null;

        barcode = BarcodeFactory.createCode39(barcodeText, false);
        barcode.setBarWidth(BARCODE_WIDTH);
        barcode.setBarHeight(BARCODE_HEIGHT);
        image = new BufferedImage(IMAGE_WIDTH, IMAGE_HEIGHT, BufferedImage.TYPE_BYTE_GRAY);
        g = (Graphics2D) image.getGraphics();
        barcode.draw(g, 10, 56);
        g.dispose();

        try {
            if(TYPE_TRANS){//諛붿퐫�� �щ챸 泥섎━
                BufferedImage source = BarcodeImageHandler.getImage(barcode);
                Image imageTrans = makeColorTransparent(source, new Color(COLOR_R,COLOR_G,COLOR_B));
                image = imageToBufferedImage(imageTrans);
                ImageIO.write(image, TYPE_IMAGE, new File(SAVE_FULL_PATH));
            }else{
                if(TYPE_IMAGE.toLowerCase().equals("png")){
                    BarcodeImageHandler.savePNG(barcode, new File(SAVE_FULL_PATH));
                }else if(TYPE_IMAGE.toLowerCase().equals("gif")){
                    BarcodeImageHandler.saveGIF(barcode, new File(SAVE_FULL_PATH));
                }else if(TYPE_IMAGE.toLowerCase().equals("jpeg") || TYPE_IMAGE.toLowerCase().equals("jpg")){
                    BarcodeImageHandler.saveJPEG(barcode, new File(SAVE_FULL_PATH));
                }else{
                    BarcodeImageHandler.savePNG(barcode, new File(SAVE_FULL_PATH));
                }
            }
        } catch(Exception e) {
            e.printStackTrace();
        }
    }


    /**
     * <pre>
     * 諛붿퐫�� �대�吏� �앹꽦 Base64 臾몄옄��
     * </pre>
     *
     * @param barcodeText 諛붿퐫�� 蹂��섑븷 臾몄옄��
     * @throws Exception
     */
    public static String getBarcodeBase64(String barcodeText) throws Exception{
        TYPE_TRANS = false;
        return createBarcodeImageBase64(barcodeText);
    }

    /**
     * <pre>
     * 諛붿퐫�� �대�吏� �앹꽦 Base64 臾몄옄��
     * </pre>
     *
     * @param barcodeText 諛붿퐫�� 蹂��섑븷 臾몄옄��
     * @param transType   諛곌꼍 �щ챸(0-遺덊닾紐�,1-�щ챸)
     * @throws Exception
     */
    public static String getBarcodeBase64(String barcodeText, Boolean transType) throws Exception{

        TYPE_TRANS = transType;

        return createBarcodeImageBase64(barcodeText);
    }


    /**
     * <pre>
     * 諛붿퐫�� �대�吏� �앹꽦 Base64 臾몄옄��
     * </pre>
     *
     * @param barcodeText 諛붿퐫�� 蹂��섑븷 臾몄옄��
     * @param boacodeWidth  諛붿퐫�� �볦씠
     * @param boacodeHeight 諛붿퐫�� �믪씠
     * @throws Exception
     */
    public static String getBarcodeBase64(String barcodeText, int boacodeWidth, int boacodeHeight ) throws Exception{
        TYPE_TRANS = false;
        BARCODE_WIDTH = boacodeWidth;
        BARCODE_HEIGHT = boacodeHeight;

        return createBarcodeImageBase64(barcodeText);
    }


    /**
     * <pre>
     * 諛붿퐫�� �대�吏� �앹꽦 Base64 臾몄옄��
     * </pre>
     *
     * @param barcodeText   諛붿퐫�� 蹂��섑븷 臾몄옄��
     * @param transType     諛곌꼍 �щ챸(0-遺덊닾紐�,1-�щ챸)
     * @param boacodeWidth  諛붿퐫�� �볦씠
     * @param boacodeHeight 諛붿퐫�� �믪씠
     * @throws Exception
     */
    public static String getBarcodeBase64(String barcodeText, Boolean transType, int boacodeWidth, int boacodeHeight ) throws Exception{

        TYPE_TRANS = transType;
        BARCODE_WIDTH = boacodeWidth;
        BARCODE_HEIGHT = boacodeHeight;

        return createBarcodeImageBase64(barcodeText);
    }

    /**
     * <pre>
     * 諛붿퐫�� �대�吏� �앹꽦(Base64 Encode)
     * </pre>
     *
     * @param barcodeText   諛붿퐫�� 臾몄옄��
     * @throws Exception
     */
    public static String createBarcodeImageBase64(String barcodeText) throws Exception{

        Barcode barcode = null;
        BufferedImage image = null;
        Graphics2D g = null;

        barcode = BarcodeFactory.createCode39(barcodeText, false);
        barcode.setBarWidth(BARCODE_WIDTH);
        barcode.setBarHeight(BARCODE_HEIGHT);
        image = new BufferedImage(IMAGE_WIDTH, IMAGE_HEIGHT, BufferedImage.TYPE_BYTE_GRAY);
        g = (Graphics2D) image.getGraphics();
        g.setColor(Color.WHITE);
        g.fillRect(0, 0, BARCODE_WIDTH, BARCODE_HEIGHT);
        barcode.setBackground(new Color(COLOR_R, COLOR_G, COLOR_B));
        barcode.draw(g, 10, 56);

        g.dispose();
        return encodeToString(BarcodeImageHandler.getImage(barcode));
    }

    /**
     * <pre>
     * �대�吏� Base64 �몄퐫�� 蹂���
     * </pre>
     *
     * @param barcodeText   諛붿퐫�� 蹂��섑븷 臾몄옄��
     * @throws Exception
     */
    public static String encodeToString(BufferedImage image) {
        String imageString = null;
        ByteArrayOutputStream bos = new ByteArrayOutputStream();

        try {
            if(TYPE_TRANS){
                BufferedImage source = image;
                //諛붿퐫�� �щ챸 泥섎━
                Image imageTrans = makeColorTransparent(source, new Color(COLOR_R,COLOR_G,COLOR_B));
                image = imageToBufferedImage(imageTrans);
            }

            ImageIO.write(image, TYPE_IMAGE, bos);
            byte[] imageBytes = bos.toByteArray();
            imageString = new String( Base64.encode( imageBytes ) );
            bos.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return "data:image/" + TYPE_IMAGE + ";base64," + imageString;
    }


    /**
     * <pre>
     * �대�吏� 諛곌꼍 �щ챸泥섎━�� �대�吏� ���� 蹂�寃�
     * </pre>
     *
     * @param image      �대�吏�
     */
    private static BufferedImage imageToBufferedImage(Image image) {
        BufferedImage bufferedImage = new BufferedImage(image.getWidth(null), image.getHeight(null), BufferedImage.TYPE_INT_ARGB);
        Graphics2D g2 = bufferedImage.createGraphics();
        g2.drawImage(image, 0, 0, null);
        g2.dispose();
        return bufferedImage;
    }

    /**
     * <pre>
     * �대�吏� 諛곌꼍 �щ챸 泥섎━
     * </pre>
     *
     * @param im      �대�吏�
     * @param color   而щ윭 肄붾뱶
     */
    public static Image makeColorTransparent(BufferedImage im, final Color color) {
        ImageFilter filter = new RGBImageFilter() {
            // the color we are looking for... Alpha bits are set to opaque
            public int markerRGB = color.getRGB() | 0xFF000000;
            public final int filterRGB(int x, int y, int rgb) {
                if ((rgb | 0xFF000000) == markerRGB) {
                    // Mark the alpha bits as zero - transparent
                    return 0x00FFFFFF & rgb;
                } else {
                    // nothing to do
                    return rgb;
                }
            }
        };
        ImageProducer ip = new FilteredImageSource(im.getSource(), filter);
        return Toolkit.getDefaultToolkit().createImage(ip);
    }

}

 
 
 
 
 
5) 테스트
톰캣 올라가면 포스트맨에서 API 호출해본다.

 
 
아래처럼 결과가 잘 나온다.

 
 
위의 링크 전체를 복사해서
(궁금하신 분들 위해 첨부)

더보기

data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAQIAAAA8CAMAAAC6lkQLAAADAFBMVEUAAAAAADMAAGYAAJkAAMwAAP8AMwAAMzMAM2YAM5kAM8wAM/8AZgAAZjMAZmYAZpkAZswAZv8AmQAAmTMAmWYAmZkAmcwAmf8AzAAAzDMAzGYAzJkAzMwAzP8A/wAA/zMA/2YA/5kA/8wA//8zAAAzADMzAGYzAJkzAMwzAP8zMwAzMzMzM2YzM5kzM8wzM/8zZgAzZjMzZmYzZpkzZswzZv8zmQAzmTMzmWYzmZkzmcwzmf8zzAAzzDMzzGYzzJkzzMwzzP8z/wAz/zMz/2Yz/5kz/8wz//9mAABmADNmAGZmAJlmAMxmAP9mMwBmMzNmM2ZmM5lmM8xmM/9mZgBmZjNmZmZmZplmZsxmZv9mmQBmmTNmmWZmmZlmmcxmmf9mzABmzDNmzGZmzJlmzMxmzP9m/wBm/zNm/2Zm/5lm/8xm//+ZAACZADOZAGaZAJmZAMyZAP+ZMwCZMzOZM2aZM5mZM8yZM/+ZZgCZZjOZZmaZZpmZZsyZZv+ZmQCZmTOZmWaZmZmZmcyZmf+ZzACZzDOZzGaZzJmZzMyZzP+Z/wCZ/zOZ/2aZ/5mZ/8yZ///MAADMADPMAGbMAJnMAMzMAP/MMwDMMzPMM2bMM5nMM8zMM//MZgDMZjPMZmbMZpnMZszMZv/MmQDMmTPMmWbMmZnMmczMmf/MzADMzDPMzGbMzJnMzMzMzP/M/wDM/zPM/2bM/5nM/8zM////AAD/ADP/AGb/AJn/AMz/AP//MwD/MzP/M2b/M5n/M8z/M///ZgD/ZjP/Zmb/Zpn/Zsz/Zv//mQD/mTP/mWb/mZn/mcz/mf//zAD/zDP/zGb/zJn/zMz/zP///wD//zP//2b//5n//8z///8SEhIYGBgeHh4kJCQqKiowMDA2NjY8PDxCQkJISEhOTk5UVFRaWlpgYGBmZmZsbGxycnJ4eHh+fn6EhISKioqQkJCWlpacnJyioqKoqKiurq60tLS6urrAwMDGxsbMzMzS0tLY2Nje3t7k5OTq6urw8PD29vb8/PwgKWLDAAAAkElEQVR4Xu3QsQ2AMAxFwYNt2H8Y1oEUaSA1Dc9SLCtfipzbOHHc/TDm2ed556vkmY771WtfJHNa7bb66e73FUEEEYhABCIQgQhEIAIRiEAEIhCBCEQgAhGIQAQiEIEIRCACEYhABCIQgQhEIAIRiEAEIhCBCEQgAhGIQAQiEIEIRCACEYhABCIQgQhEIIK7LuCqMXj3tsQgAAAAAElFTkSuQmCC

 
 
 
 
크롬 등 아무데서나 url을 쳐서 이동하면

쨔쟌!!!! 바코드가 잘 출력된다.
 
 
 
 
혹싀 모르니 바코드 인식까지 해보면
https://products.aspose.app/barcode/ko/recognize#/recognized

ㅋF,,,,,, 파람으로 읽어들인 92000001이 잘 뽑힌다.