내친김에 진행했던 Spirng 프로젝트들을 배포해보고 싶어서 찾아보다가

Synology로 배포가 가능하다는 것을 발견.... 완전 만능이다.

Synology 자체가 Linux 기반이라 그런지 확장성이 무궁무진하다.

 

어쨌든,  React + Node.js로 토이프로젝트를 진행 중이었는데

지난번에 구축했던 데이터서버도 성공적으로 적용 해보았고,

Node.js 서버도 구축해보면 좋겠다 싶어서 시도해보았다.

 

개발환경

https://www.youtube.com/playlist?list=PLRx0vPvlEmdD1pSqKZiTihy5rplxecNpz

 

React와 Node.js를 활용한 고객 관리 시스템 개발 강의

 

www.youtube.com

 

React 복습도 할 겸, Node.js도 간단하게 배워볼 겸 해서 이 강의를 모두 듣고 토이 프로젝트를 완성했다.

24년 9월기준, 영상에 나오는 React와 Node.js의 버전에서는 현재 지원되지 않는 라이브러리들이 있어서 

React 최신 안정화 버전 18.3.1, Node.js는 22.9.0 버전으로 진행했다.

 

Node.js에 대한 이해가 전혀 없었는데, 강의를 수강하면서 조금은 이해가 됐기때문에 서버를 구축할 수 있었다.

 

Node.js 설치하기

  • 우선 Synology Nas 패키지 센터에서 Docker를 설치한다. Docker의 활용도가 정말 무궁무진하다.

 

 

  • 그리고 node를 관리할 폴더 하나를 만들어야한다. docker 라는 최상위 디렉토리 안에 node 폴더를 생성해주었고, 현재 진행중인 프로젝트 이름인 management 폴더를 하나 더 만들어 주었다. 폴더 안에 있어야할 파일들은 이따가 자세히 설명할 것이기 때문에 일단 빈 폴더들만 만들어주면 된다.

  • Dokcer 설치가 완료되었으면 Docker를 열고, 레지스트리에서 node를 검색하고 자신의 개발환경에 맞는 node 버전을 설치하면 된다. (cmd창에서 node -v 입력하고 node 버전 확인.)
  • 나는 22.9.0 버전에서 개발을 진행했기 때문에 같은 버전으로 설치해주었다. 최신버전이 필요하다면 latest로 설치.

 

  • 설정페이지가 열리면 일반 설정에서는 딱히 건드릴 것은 없는 것 같고, 포트설정도... 어차피 ssh로 Node.js를 빌드하는 과정에서 포트를 지정해주면 되기 때문에 그냥 빈 포트 1111로 막 설정해주었다. 볼륨설정이 가장 중요하다.

  • 폴더추가에서 아까 만든 디렉토리를 설정한다. 나는 /docker/node/management 였다. 그리고 마운트 경로에 Node.js와 매핑되는 /home/node/app 이라는 경로를 입력해주면 설치 단계는 끝난다.

 

 

Node.js 서버 Build를 위한 준비

이제 /docker/node/management 디렉토리 안에 Build를 위해 필요한 파일들을 넣고 Build를 진행하면 서버가 구축된다.

어떤 파일들이 필요한지 알아볼 것이다.

 

  • 우선 Dockerfile 이라는 파일이 필요하다. 
    • IntelliJ 기준, 프로젝트 제일 상위 디렉토리에 Dokcerfile 이라는 파일을 만들어줘야 한다.
    • 우클릭 - New - file에서 확장자는 따로 지정하지 않고 Dockerfile 이라는 이름의 파일을 만들어 준다.
    • Dockerfile 안에  docker 명령어들을 입력해준다. 아래는 예시
# Node.js 이미지 설정
FROM node:22.9.0

# 작업 디렉토리 생성
WORKDIR /usr/src/app

# 패키지 파일을 복사
COPY package*.json ./

# 의존성 설치
RUN yarn install
RUN npm install
RUN npm install -g nodemon

# 애플리케이션 소스 복사
COPY . .

# 포트 노출
EXPOSE 6000

# 애플리케이션 시작
CMD ["yarn", "run", "server"]

 

여기서 확인할 것은 본인 환경과 맞는 Node.js 버전을 맞춰주는 것, yarn / npm 등, 프로젝트의 서버에서 필요한 의존성을 설치해 주는 것(동영상 강의에서 yarn을 사용해서 여기에도 yarn을 설치해주었다.), 사용할 포트를 지정하는 것, 세가지다.

Synology Nas에서 기본적으로 5000포트를 사용하기 때문에 나는 편의상 6000번 포트를 Node.js 서버에 사용하려고 6000번 포트를 지정해서 Dockerfile을 성공적으로 생성했다.

 

  • 다음으로 필요한 파일은 package.json 파일이다. 아마 프로젝트를 만들면서 작성했기 때문에 파일만 복사해서 가져오면 될 것 같다. 스크립트에 "server": "nodemon server.js" 라고 작성해 두었기 때문에 CMD ["yarn", "run", "server"] 명령어를 통해 server.js라는 파일을 실행하게 된다.
{
  "name": "management",
  "version": "1.0.0",
  "scripts": {
    "client": "cd client && yarn start",
    "server": "nodemon server.js",
    "dev": "concurrently --kill-others-on-fail \"yarn server\" \"yarn client\"",
    "start": "node server.js"
  },
  "dependencies": {
    "@mui/icons-material": "^6.1.1",
    "basic-ftp": "^5.0.5",
    "body-parser": "1.18.3",
    "express": "^4.21.0",
    "http-proxy-middleware": "^3.0.2",
    "mariadb": "^3.3.2",
    "multer": "^1.4.5-lts.1",
    "mysql": "^2.18.1",
    "path": "^0.12.7"
  },
  "devDependencies": {
    "concurrently": "^4.0.1"
  },
  "description": "React와 node.js로 만든 고객 관리 시스템 입니다.",
  "main": "server.js",
  "keywords": [],
  "author": "",
  "license": "ISC"
}
  • 그리고 database.json 파일도 필요하다. 로컬에서 Node.js 서버를 DataBase와 연결할 때 작성했던 파일을 그대로 가져오면 된다.
{
  "host": "시놀로지id.synology.me",
  "user": "id",
  "password": "pw",
  "port": "3306",
  "database": "management"
}
  • yarn.lock 혹은 package-lock.json 파일도 필요하다. 두 파일을 혼용해서 사용하면 충돌이 일어날 수 있기 때문에 npm대신 yarn을 썼으면 yarn.lock파일을, npm을 사용한다면 package-lock.json 파일을 가져오도록 한다.

 

  • 마지막은 제일 중요한 server.js 파일이다. 
const fs = require('fs');
const express = require('express');
const bodyParser = require('body-parser');
const app = express();
const port = process.env.PORT || 6000;
const ftp = require('basic-ftp');

app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true}));

const data = fs.readFileSync('./database.json');
const conf = JSON.parse(data);
const mysql = require('mysql');

const connection = mysql.createConnection({
    host: conf.host,
    user: conf.user,
    password: conf.password,
    port: conf.port,
    database: conf.database
});
connection.connect();

 

이런식으로 쭉 작성된 server.js파일인데, Dockerfile에서 지정해준 6000번 포트를 잘 확인해주면 된다.

const port = process.env.PORT || 6000; 

database.json 파일을 이용해 DataBase에도 성공적으로 연결 해주었다면 이제 Build를 위한 준비는 끝났다.

아래는 최종 파일 목록이다.

Node.js 서버 빌드를 위한 필수파일들

 

여기까지 준비가 끝났으면 ssh에 접속해서, node 이미지를 만들고, build를 해보자.

 

Node.js 서버 Build 후 Run하기

cmd의 ssh 명령어를 통해 Synology Nas 내부에 접근해야 한다.

  • 우선 제어판 - 터미널 및 SNMP - 터미널 - SSH 서비스 활성화 체크 - 포트 : 22 이렇게 설정해준다.

제어판 - 터미널 및 SNMP - 터미널

 

  • 이제 명령프롬프트(cmd)를 켜고, Synology에 접속한다. 
    • 명령어는 ssh 시놀로지id@192.168.x.x (시놀로지 내부ip)
    • 시놀로지 내부ip 확인하는 방법은 https://nowcow.tistory.com/1 이 포스트에 있다.
    • 패스워드까지 입력하고 나면 시놀로지id@시놀로지id : ~$ 이렇게 접속된걸 확인할 수 있다.
    • 패스워드 입력할때는 화면에 아무런 반응이 없지만 그냥 입력후 엔터를 치면 된다.

 

  • 접속이 완료됐다면 /dokcer/node/management/ 라는 디렉토리로 찾아가보자.
    • 바로 cd /docker/~~ 입력하면 안되고 최상단에 /volume1/ 이라는 디렉토리를 거쳐야 한다.
    • cd /volume1/docker/node/management/ 

 

  • 명령어를 입력해서 ~~/management/ 디렉토리 안에 있는 파일들을 Build 해보자.
  • 원래는 docker ~~ 명령어를 통해서 진행하지만, ssh 접속할 때 admin계정으로 접속하지 않아서 권한이 없다. docker 명령어 앞에 sudo를 붙여 해결한다.
    • sudo docker build -t 식별가능한이름 . ex) sudo doceker build -t management-server .

 

Dockerfile을 읽어서 안에 설정해둔 값으로 Build가 잘 된것을 확인할 수 있다. 나는 여러가지 의존성을 더 추가해주었기 때문에 약간의 시간도 소모되었고, Build 과정은 각자 다를 수 있다. 

 

  • 이제 Build된 이미지를 Run 해줄 차례이다.
    • sudo docker run -d -p 6000:6000 이름 ex) sudo docker run -d -p 6000:6000 management-server
    • 6000:6000의 뜻은 외부 6000번 포트로 들어온 요청을 내부 컨테이너 6000번 포트로 매핑한다는 뜻이다.
    • (외부6000):(내부6000) 이렇게 이해하면 된다.

 

이제 Docker - 컨테이너 에서 방금 Run 한 management-server가 실행되고 있다면 성공!!

 

 

management-server를 더블클릭해서 로그탭을 보면 제대로 실행되었는지 로그 확인도 가능하다.

 

서버가 잘 돌아가는지 조금 더 시각적으로 확인해 볼 필요가 있다.

 

  • 나는 server.js 파일에 테스트를 위한 api를 하나 만들어두었다.
// test용 get요청
app.get('/api/test', (req, res) => {
    res.send('Hello World! Welcome to the API!');
});
  • curl http://시놀로지id.synology.me:6000/api/test 명령어를 입력했을 때 정상적으로 출력되는 것을 볼 수 있다. 

 


이렇게 Synology Nas에 24시간 구동되는 Node.js 서버를 구축했다.

컨테이너를 실행하는 동안은 서버가 계속 구동되기 때문에 yarn server 등의 실행 명령어가 필요없어진다.

이제 client 단에서 proxy 설정을 통해 우리가 만든 서버와 매핑해주면 정상적으로 데이터를 불러올 수 있게 된다.

DataBase 서버를 성공적으로 구축하여 팀 프로젝트에서 잘 사용중이었다.
문득 파일 업로드 기능도 웹서버를 구축하여 입/출력 하면 어떨까 생각이 들었다.
간할거라 생각했는데 몇일동안 머리를 싸매고 시행착오를 겪었다...

 

Mark Ⅲ에서 파일을 FTP 서버에 입력(업로드) 하는 것을 성공했고,

Mark Ⅳ에서 파일을 출력(다운로드) 하는 것을 성공했다.

아래는 Mark Ⅰ~ Ⅳ 까지의 해결 과정이다.

 

프로젝트의 기능 중에 파일을 업로드를 담당하는 class

@Configuration
public class WebConfig implements WebMvcConfigurer {

    private String resourcePath = "/upload/**";
    private String savePath = "file:///c:/image/";

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry){
        registry.addResourceHandler(resourcePath)
                .addResourceLocations(savePath);
    }
}

 

이런 식으로 Webconfig class를 이용해서 c:/image라는 로컬 경로에서 업로드된 파일을 관리한다.

처음에는 단순하게 Synology Nas에서 제공하는 WebDav나 FTP를 네트워크 드라이브로 연결해서 사용하면 되겠지 했다.

 

 

Mark Ⅰ

RaiDrive 등의 FTP 연결 프로그램을 설치해서, FTP서버를 로컬 z드라이브로 할당하고, 위의 경로만 z://image로 바꿔서 사용하면 다른 설정은 건드릴 필요 없이 간단하게 FTP서버를 이용할 수 있다.

 

문제점

  1. 프로그램을 설치해서 z드라이브를 할당해야하기 때문에 z드라이브를 사용중인 컴퓨터에서는 환경을 또 바꾸어주어야 한다.
  2. 팀원 모두가 RaiDrive같은 프로그램을 따로 설치해야하기 때문에 번거롭다.. (이런 과정이귀찮은 사람이 생길 수 있음)
  3. 추후에 만약 완성된 프로젝트를 배포한다고 했을 때... 그때는 어떻게 해야할 것인가에 대한 문제점.

실제로 이 방법으로 프로젝트를 완성하긴 했다. 다만 3번 문제가 제일 신경쓰여서 나 혼자 develop 해보기로 결정했다.

 

Mark Ⅱ

FTP를 쓰면 어쨌든 네트워크 드라이브를 할당 해주어야 하기 때문에 이거 말고 WebDav 서비스를 이용해서 c:/ , z:/ 이런 경로 말고 http 프로토콜을 이용해보려고 했다.

 

이렇게 기본 5005, 5006 포트를 개방하고, 

http://시놀로지id.synology.me:5005/ 경로로 이동하면 미리 설정한 폴더로 접속이 돼야하는데...

 

문제점

  1. 이 방법은 출력(다운로드)은 가능한데 입력(업로드)이 안되는 것 같다...
    • FTP서버처럼 네트워크 드라이브를 할당해서 업로드를 하는 방식임..
    • image.jpg라는 파일을 출력할 때 http://시놀로지id.synology.me:5005/image.jpg 이렇게 하면 출력이 돼야하는데 익멱 WebDav를 활성화 해도 자꾸 로그인을 하라고 나온다... 아이디에 anonymous를 입력해야 정상 출력됨
  2. http://anonymous@시놀로지id.synology.me:5005/image.jpg 이런식으로 아이디 값을 지정해주면 출력은 가능한데 입력은 불가능하고, 익명에 대한 폴더와 권한을 지정해주긴 했지만 인증 정보를 포함하는 HTTP Basic Authentication은 보안상의 이유로 권장되지 않는다고 한다. 

짧게 정리되어 있지만 실제로 제일 시간을 많이 뺏긴 부분이었다. 결국 이 방법은 사용하지 못하고 이것저것 검색하다가 눈에 띈 방법이 있다...

 

Mark Ⅲ (파일 업로드 성공)

Spring Boot에 FTP를 사용할 수 있는 라이브러리가 있다는 것... 그러면 네트워크 드라이브를 따로 할당하지 않고 FTP 서버로 접근이 가능하다. 

 

버전은 각자 맞게 최신 버전으로 검색해서 [Apache Commons Net] 라이브러리를 dependencies에 추가해준다.

implementation 'commons-net:commons-net:3.8.0'

 

원래는

implementation 'commons-io:commons-io:2.11.0

 

이 라이브러리를 사용했는데,

위의 라이브러리를 사용하면 FTP, SMTP, POP3, IMAP, Telnet, NTP와 같은 네트워크 프로토콜을 쉽게 구현할 수 있게 해준다고 한다.

  • 일단 Synology에서 FTP 기능을 활성화 시킨다.
    • 기본포트 21을 사용해도 무방하지만, 랜덤 공격에 당할 수 있다고 하니 임의로 2200포트를 사용했다.
    • 기본 포트 범위 사용으로 하면 라우터구성 할 때 자꾸 에러나서 그냥 55555포트를 임의로 설정.

제어판 - 파일서비스 - FTP

 

  • 그리고 조금 아래로 내려보면 '고급 설정'이 있다.
    • 익명 FTP 활성화 체크
    • 익명 루트 변경 체크
    • 업로드된 파일을 관리할 폴더 하나를 지정해준다 (나는 web 이라는 폴더 만들어서 지정.)
    • 이렇게 되면 FTP를 anonymous(익명)으로 접속하면 web 폴더에만 읽기/쓰기가 가능해진다.

web이라는 공유폴더를 사용중이다.

 

  • 이제 라우터 구성, 방화벽, 포트폴리오 설정을 해주어야 한다.
    • 제어판 - 외부 액세스 - 생성 -  내장 응용 프로그램 - FTP 파일 서버 선택 - 적용

 

 

  • 사용중인 공유기 설정 페이지에서 2200포트도 포트포워딩 해준다.

 

내부 ip 등 자세한 포트포워딩 정보는 (https://nowcow.tistory.com/1) 이곳에 포스팅 해두었다.

이렇게 FTP 서버를 구축하고, 외부 접속이 가능하게 하는 설정은 모두 끝났고 이제 프로젝트에 적용해보자.

 

 

이제 Spring 프로젝트에서 FTP 클래스를 하나 만든다. 나는 그냥 FtpUtil 이렇게 이름 지었다.

  • 나는 의존성 주입 없이 Java 클래스를 객체로 생성하여 사용하는 방식으로 했다.
import org.apache.commons.net.ftp.FTP;
import org.apache.commons.net.ftp.FTPClient;

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

public class FtpUtil {
    private static final String FTP_SERVER = "시놀로지ip.synology.me"; // FTP 서버 주소
    private static final int FTP_PORT = 2200; // FTP 포트 번호
    private static final String FTP_USER = "anonymous";    // FTP 사용자명
    private static final String FTP_PASS = "";    // FTP 비밀번호

    private FTPClient ftpClient;

    public FtpUtil() {
        ftpClient = new FTPClient();
    }

    // FTP 서버에 연결
    public void connect() throws IOException {
        ftpClient.connect(FTP_SERVER, FTP_PORT); // 서버 주소와 포트
        boolean success = ftpClient.login(FTP_USER, FTP_PASS);
        if (!success) {
            throw new IOException("FTP 서버 로그인 실패");
        }
        ftpClient.enterLocalPassiveMode();  // Passive 모드 사용
        ftpClient.setFileType(FTP.BINARY_FILE_TYPE);  // 바이너리 파일 타입 설정
    }

    // 파일 업로드
    public boolean uploadFile(String remoteFilePath, File localFile) throws IOException {
        try (InputStream inputStream = new FileInputStream(localFile)) {
            boolean done = ftpClient.storeFile(remoteFilePath, inputStream);
            if (!done) {
                throw new IOException("파일 업로드 실패");
            }
            return true;
        }
    }

    // 파일 다운로드
    public void downloadFile(String remoteFilePath, String localFilePath) throws IOException {
        try (FileOutputStream fos = new FileOutputStream(localFilePath)) {
            boolean success = ftpClient.retrieveFile(remoteFilePath, fos);
            if (!success) {
                throw new IOException("파일 다운로드 실패");
            }
        }
    }

    // 파일 삭제
    public boolean deleteFile(String remoteFilePath) {
        try {
            // FTP 서버에서 파일 삭제 로직
            ftpClient.deleteFile(remoteFilePath);
            return true; // 삭제 성공 시 true 반환
        } catch (IOException e) {
            e.printStackTrace();
            return false; // 삭제 실패 시 false 반환
        }
    }


    // FTP 연결 종료
    public void disconnect() {
        if (ftpClient.isConnected()) {
            try {
                ftpClient.logout();
                ftpClient.disconnect();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

 

익명 로그온을 활성화 해두었기 때문에 사용자명에 anonymous만 입력해주어도 접근이 가능해진다. 

이제 이렇게 contoller 혹은 service 에서 newFtpUtil()로 인스턴스를 생성해서 사용하면 된다.

 

실제로 적용된 코드 ↓

더보기

Contoller

    @PostMapping("/post/save")
    public String save(@ModelAttribute PostDTO postDTO, HttpSession session) throws IOException {

        // 게시물 저장
        postService.save(postDTO);

        // 세이브 된 글 번호로 리다이렉트
        int firstPostNum = postService.findFirstPostNum(); // 첫 번째 게시물 번호 가져오기
        System.out.println("firstPostNum = " + firstPostNum);
        return "redirect:/post/" + firstPostNum;
    }

 

Service

public void save(PostDTO postDTO) throws IOException {
        if (postDTO.getPost_upLoadFile().isEmpty()) {
            PostEntity postEntity = PostEntity.toSaveEntity(postDTO);
            postRepository.save(postEntity);
        } else {
            PostEntity postEntity = PostEntity.toSaveFileEntity(postDTO);
            Integer postNum = postRepository.save(postEntity).getPostNum();
            PostEntity post = postRepository.findById(postNum).get();

            FtpUtil ftpUtil = new FtpUtil();
            try {
                ftpUtil.connect(); // FTP 서버 연결
                for (MultipartFile postFile : postDTO.getPost_upLoadFile()) {
                    String originalFilename = postFile.getOriginalFilename();

                    if (originalFilename != null && !originalFilename.isEmpty()) {
                        // 확장자 추출
                        String fileExtension = originalFilename.substring(originalFilename.lastIndexOf("."));
                        // 랜덤 파일 이름 생성
                        String randomFileName = UUID.randomUUID().toString() + fileExtension;

                        // 임시 파일로 서버에 저장
                        File tempFile = new File(System.getProperty("java.io.tmpdir") + "/" + randomFileName);
                        postFile.transferTo(tempFile);

                        // FTP 서버에 파일 업로드
                        ftpUtil.uploadFile("/marketFile/" + randomFileName, tempFile);

                        // FileEntity 생성 및 저장
                        FileEntity fileEntity = FileEntity.toFileEntity(post, randomFileName);
                        fileRepository.save(fileEntity);

                        // 업로드 후 임시 파일 삭제
                        if (tempFile.exists()) {
                            tempFile.delete();
                        }
                    } else {
                        System.out.println("파일이 입력되지 않았습니다! 파일명: " + originalFilename);
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
                // 파일 업로드 중 발생한 오류 처리
            } finally {
                ftpUtil.disconnect(); // FTP 서버 연결 해제
            }
        }
    }

 

Contoller는 Service를 불러오는 부분이고, 실제로 Service의 public void save 메소드에서 FtpUtil을 불러온다.

나는 혹시나 파일명이 겹치게 될까봐 UUID 라는 라이브러리를 사용해서 파일의 이름을 랜덤하게 지정하고 

업로드하는 방식을 사용했다. 순서는 이렇다

 

FtpUitl 초기화 - FTP서버 연결 - 파일이 업로드 되면 랜덤으로 이름 생성 - 임시파일을 로컬 저장소에 저장 - FTP로 업로드 - 임시파일 삭제 - FTP 연결해제

 

이 로직으로 돌아가는데, 중간에 괜히 임시파일을 로컬에 저장하는 이유는 FTP로 파일을 전송할 때 파일의 경로가 필요하기 때문이라고 한다. 또 에러 관리 및 유지보수를 위해서 라고도 하는데 이 부분은 조금 더 서치가 필요할 것 같다.

각자의 프로젝트에 맞게 코드를 알맞게 수정 및 적용하면 될 것 같고, 

// FTP 서버에 파일 업로드
ftpUtil.uploadFile("/marketFile/" + randomFileName, tempFile);

나는 /web/ 디렉토리 안에 /markerFile/ 이라는 폴더에 저장하고싶어서 저렇게 디렉토리 경로를 추가했다.

이렇게 파일 업로드(입력)하는데에 성공하고 이제 이걸 다운로드(출력) 하려고 하는데,,, 이게 또 쉽지않다...

 

Mark Ⅳ (파일 업로드/다운로드 모두 가능)

<div style="width: 700px; display: flex; justify-content: center">
	<div th:each="index : ${#numbers.sequence(0, detail.file_url.size() - 1)}">
		<img th:id="'thumbnail_' + ${index}" th:src="@{|/upload/${detail.file_url[__${index}__]}|}"
			style="width: 80px; height: 80px; margin: 15px; box-shadow: 0px 0px 8px -1px;
			border-radius: 8px; cursor: pointer" th:onclick="'extend('+ ${index} +')'" th:onmouseover="'extend('+ ${index} +')'">
	</div>
</div>

 

원래는 이 글 맨 위에 보여준 WebConfig 클래스를 만들고, 이런식으로 /upload/ 경로로 요청되는 것들을 c:/image/로

대치해서 로컬의 c:/image 디렉토리 안에 있는 파일을 불러왔었다.

그렇게 되면 http://localhost:8080/image/사진.jpg 이런 식의 경로가 된다.

우리는 로컬환경을 쓰지 않으려고 했던 것이기 때문에 FTP를 이용해 Synology에 업로드했던 파일을 가져와야한다.

 

어떻게.....?

진짜 이 부분에서 애를 많이 먹었다.

http://localhost:8080/ 부분을 내 WebDav 경로로 대치했을때 사용자 정보가 없어서 아무 이미지도 로드되지 않았다..

http://anonymous@시놀로지id.synology.me:5005/image.jpg 이 url로 접속하면 사진이 불러와 졌던걸 생각해서

http://anonymous@시놀로지id.synology.me:5005/ 이 경로 대치를 했는데도 계속 이미지가 로드되지 않았다...

Thymeleaf에서 anonymous@ 이 부분을 인식하지 못하는 것 같기도 하고 고민이 참 많았다.

FTP에선 익명 로그인을 지원하는데 WebDav에서는 익명 로그인을 사용하지 못하는건지...

Thymeleaf 탬플릿에서 WebDav 연결하는 유틸리티 클래스를 연결해줘야하는건지.... 도무지 감이 잡히지 않다가 

결국 https://digitalogia.tistory.com/367 이 블로그에서 해답을 찾았다...

자세한 세팅법은 위의 블로그를 참고하시면 된다. 

Web Staion

나는 정확히 뭔지도 모르는 Web Staion이 이미 세팅되어있었고, 방화벽 규칙에 추가해주고

iptime 설정에서 80포트를 포트포워딩 해주었다.

내가 /web/marketFile/ 이라는 디렉토리 경로를 사용하는것에 대한 의문이 풀렸을 것이다.

http://시놀로지id.synology.me/image.jpg 경로를 주소창에 입력하면

시놀로지 파일스테이션에 있는 /web/ 이라는 공유폴더가 Root 디렉토리로 설정되어서

/web/image.jpg 라는 파일이 출력되는걸 알 수 있다.. 정말 이렇게 간단한 일일 줄이야...

/web/ 폴더 안에 모든 파일이 계속 업로드 된다면 관리가 쉽지 않을 것 같아서 진행중인 프로젝트의 이름을 따서

/marketFile/ 이라는 디렉토리를 이 프로젝트 전용 디렉토리로 사용했다.

 

이제 저 디렉토리 안에 파일이 있으면 손쉽게 출력이 가능할 것이다.

 

<div style="width: 700px; display: flex; justify-content: center">
	<div th:each="index : ${#numbers.sequence(0, detail.file_url.size() - 1)}">
		<img th:id="'thumbnail_' + ${index}" th:src="@{|http://시놀로지id.synology.me/marketFiles/${detail.file_url[__${index}__]}|}"
			style="width: 80px; height: 80px; margin: 15px; box-shadow: 0px 0px 8px -1px;
			border-radius: 8px; cursor: pointer" th:onclick="'extend('+ ${index} +')'" th:onmouseover="'extend('+ ${index} +')'">
	</div>
</div>

 

그리고 코드를 바꿔서 Mark Ⅳ 단계에서 프로젝트의 DataBase와, 파일 입/출력 기능을 로컬에서 웹서버로 전환 완료했다.


우선 테스트 목적으로 진행된 작업이라 보안에 크게 신경쓰지 않았다.

anonymous 계정에 지정된 폴더만 접근할 수 있게 권한을 주는 정도로만 진행했는데

보안을 생각한다면 익명이 아니라 프로젝트에 맞는 계정을 새로 생성하고 접근 권한도 따로 부여하고,

GitHub에 공개되지 않게 ignore에 추가도 해야할 것이다.

 

조금 더 나은 방법이 있을 것이고 틀린 방법일 수도 있지만 개인 프로젝트를 진행하기에는 충분할 정도의 기능을 구현했다.

 

이제 이렇게 완성된 프로젝트를 Synology를 이용해서 배포까지 해 볼것이다!

'웹서버' 카테고리의 다른 글

Synology Nas에 Docker로 Node.js 서버 구축하기  (5) 2024.10.09

팀 프로젝트를 진행하면서 제일 답답했던 부분중에 하나가 데이터베이스 관리였다.
팀원 4명이 모두 로컬에서 작업하다 보니 각자 기능테스를 하며 불필요한 데이터들이 많이 쌓였고,
데이터를 합쳐야 할 때에는 각자 데이터가 다르기때문에 dump 하기도 너무 힘들었다.
그래서 데이터베이스를 로컬 -> 서버로 이전해보기로 결정.
검색해보니 synology에 mariaDB 서버 구축이 가능하다고 해서 도전 해보았다.

 

Synology에 MariaDB설치하기

우선 패키지센터에서 mariadb를 검색하여 설치한다.

  • 이 때 PHP 8.0과 phpMyAdmin도 같이 설치된다.

혹시 설치가 안된다면 각각 검색하여 설치하도록 한다.

 

 

  • 패스워드 / 포트 설정하는 창이 나오는데, 패스워드는 root 계정에서 사용하게 될 패스워드.
    나는 3306 포트를 사용하기로 했다.

나중에 mysql 서버도 구축하려고 했는데 포트가 충돌하여 애먹었다. 결론은 3306 사용해도 된다.

 

이후에 다음화면으로 쭉 넘겨서 설치를 완료한다.

 

실행하여 TCP/IP 연결 활성화 체크 후 적용

  • 이렇게 되면 mariadb 세팅은 끝.

이렇게 끝이면 좋겠지만 외부접속을 위해 포트포워딩 / 방화벽 설정을 해주어야 한다.

 

 

외부 접속을 위해 포트 개방하기. (포트포워딩)

  • 우선 사용중인 nas의 내부ip를 확인한다.

 

시놀로지 상단에 위젯버튼을 누르면 위젯 창이 뜨는데

+ 버튼을 눌러서 시스템 상태 위젯을 활성화 시키고 내부 ip 확인한다. (192.168.0.xxx)

 


이제 각자 사용중인 공유기의 설정 페이지로 이동한다. http://192.168.0.1

나는 iptime을 이용중이라 iptime 기준으로

고급설정 - NAT/라우터 관리 - 포트포워드 설정

 

이런식으로 새 규칙을 하나 추가해준다.

규칙 이름 - 그냥 알아보기 쉽게 알아서 지정해준다.
내부 ip주소 - 마지막 파란네모에 synology에서 확인한 내부 ip 입력.
외부 포트 / 내부 포트 - 처음에 설정했던 3306포트 입력하고 적용.

 

 

그 다음은 다시 synology로 돌아와서 방화벽 규칙을 생성

제어판 - 보안 - 방화벽 - 규칙편집 - 생성 - 내장된 응용 프로그램 목록에서 선택 - MariaDB 추가

 

이렇게 되면 외부에서 접속할 수 있는 세팅은 끝난다.

 

이제 MariaDB 관리자 페이지에 접속해서 확인해보자.

아까 설치된 phpMyAdmin 접속. http://시놀로지id.synology.me/phpmyadmin/

위와 같은 페이지가 뜰텐데, 암호에 처음 설정했던 root계정 패스워드를 입력하고 접속한다.

혹시나 phpMyAdmin 접속이 안된다면 80 포트도 포트포워딩을 해준다.
mariadb 포트포워딩 했던 것처럼 이름은 대충 php로 지어주고,
내부 ip주소에 시놀로지 내부ip 입력, 외부포트 / 내부포트는 각각 80~80 / 80~80

 

 

이 화면이 떴다면 대성공.


하나 추가로 설정해주어야 할 것이 있다면 root계정을 사용하지 말고 사용자 계정을 추가해주기.

상단의 사용자 계정 페이지로 가서

 

사용자 추가를 눌러준다.

 

 

root계정 대신 사용하는 것이기 때문에 전체적 권한을 모두체크 하고 계정을 생성한다.
호스트명에 있는 %의 의미는 모든 ip에 대한 접속을 허용한다는 것이다.
ip를 제한하고 싶다면 192.168.x.x 등의 ip를 설정해주면 되는데, 팀원 모두가 사용할 것이니 %로 설정.


이렇게 모든 설정을 마쳤고, DB 서버가 제대로 구축되었는지 확인하기 위해 DBeaver 툴을 사용했다.

 

새 연결에서 MariaDB 선택하고 우리가 설정해둔 값을 입력한다.

연결 성공!

 

여기서 하나 재밌는 것은 MariaDB가 MySql 계열(?)이라서 그런지
별다른 설정 없이 MySql를 사용하는 환경에서도 DB서버가 잘 작동했다.
팀프로젝트 환경설정이 MySql로 되어있었는데 MariaDB에 대한 dependencies 추가 없이 사용 가능했다.


이제 로컬 말고 서버로 구축한 데이터베이스를 사용하면 된다!

+ Recent posts