728x90
반응형

1. Date 객체를 이용한 타임스탬프로의 전환

// 타임스탬프로 변환
function Unix_timestampConv()
{
    return Math.floor(new Date().getTime() / 1000);
}

 

2. 타임스탬프 값을 년월일로 변환

function Unix_timestamp(t){
    var date = new Date(t*1000);
    var year = date.getFullYear();
    var month = "0" + (date.getMonth()+1);
    var day = "0" + date.getDate();
    var hour = "0" + date.getHours();
    var minute = "0" + date.getMinutes();
    var second = "0" + date.getSeconds();
    return year + "-" + month.substr(-2) + "-" + day.substr(-2) + " " + hour.substr(-2) + ":" + minute.substr(-2) + ":" + second.substr(-2);
}

 

타임스탬프란 UTC 로부터 시간을 초 단위로 환산하여 나타낸 값이다. 

다시 Date 객체로 바꿔야 할 경우 반대로 1000을 곱 해주면 된다.

728x90
반응형
728x90
반응형

DB에서 데이터를 가져와 fileState 에 담아주었다면, fileState 의 file_location에 S3 파일 저장위치가 담겨있을 것이다.

( fileReducer 참고)

 

리스트 항목을 클릭했을 때 file_location이 있는 상태면 다운로드를 할 수 있게끔 구현해보자.

1. 이전 코드에 추가해준다.

<WrapperDiv
    kindOf={`input`}
>
    <TextField
        label={`사업자등록증`}
        type='file'
        color='primary'
        size='small'
        InputLabelProps={{
            shrink: true,
        }}
        name={`file_object`}
        onChange={(e: any) => {
            let reader = new FileReader();
            let file = e.target.files[0];
            reader.onloadend = () => {
                fileDispatch({ name: e.target.name, value: file });
                fileDispatch({ name: 'file_url', value: reader.result });
            };
            reader.readAsDataURL(file);
        }}
    />
</WrapperDiv>
{fileState.file_url !== '' &&
    <WrapperDiv>
        <img style={{ width: "100%", height: "100%" }} src={fileState.file_url} />
    </WrapperDiv>
}
{(!fileState.file_url && fileState.file_location !== '') &&
    <GridBoxDiv
        gtc={`1fr 1fr`}
        gap={`10px`}
        ai={`center`}
        kindOf={`input`}
    >
        <TextField
            label={`사업자등록증 파일`}
            type='text'
            color='primary'
            size='small'
            InputLabelProps={{
                shrink: true,
            }}
            value={'image.png'}
            disabled={true}
        />
        <Button
            width={`100%`}
            height={`35px`}
            padding={`0`}
            margin={`0`}
            type={`button`}
            kindOf={`greyButton`}
            hoverGrey={true}
            id={fileState.file_location}
            onClick={download}
        >
            download
        </Button>

    </GridBoxDiv>
}

 

리스트 항목을 클릭했을 때 file_url은 없는 상태(undefined)일 것이고 ( DB에 없는 내용이기 때문 ) file_location은 s3 저장위치를

나타낼 것이다.

 

2. 다운로드 버튼을 만들었고 이제 버튼 이벤트를 추가해준다.

    const download = (e: any) => {
        console.log(e.target.id);
        fetch(e.target.id, {
            method: "GET",
            headers: {}
        })
            .then(response => {
                response.arrayBuffer().then(function (buffer) {
                    const url = window.URL.createObjectURL(new Blob([buffer]));
                    const link = document.createElement("a");
                    link.href = url;
                    link.setAttribute("download", "image.png"); //or any other extension
                    document.body.appendChild(link);
                    link.click();
                });
            })
            .catch(err => {
                console.log(err);
            });
    }

 

url 데이터가 있을 때 클릭하면 다운로드를 할 수 있게 해주는 함수이다. 파일의 이름도 지정해줄 수 있다.

여기까지 내용을 정리해보면

 

  1. input type file 을 이용해 클라이언트 State 안에 file 내용을 저장하고, formData 로 만들어 백엔드에 전송한다.
  2. 백엔드에선 multer 로 file을 인식한 뒤 s3.upload 메소드로 파일을 저장해준다. 저장이 성공하면 DB에 key값이랑 location을 저장한다 (file은 인코딩 해아함 )
  3. 클라이언트에서 이미 등록된 사진을 확인할 경우 먼저 DB에 해당 key에 부합하는 데이터가 있는지 확인 후 있으면 location을 가져온다.
  4. location을 Reducer에서 s3 파일 위치로 만들어 state에 담아준다.
  5. url download 함수와 버튼을 만들어 다운로드 가능하게끔 만들어준다.

 

@@ 혹시 큰 용량의 파일이 업로드가 413에러가 뜰 때 nginx가 설치되어있다면 /etc/nginx/nginx.conf 파일에 

http {
    client_max_body_size 5M;

    ...
}

이 구문을 추가해주자.

728x90
반응형
728x90
반응형

전 포스트에서 업로드 하는 것까지 구현해보았다.

이번 포스트에서는 클라이언트에서 파일을 조회하는 것을 구현해보겠다.

 

1. 먼저 리스트 항목 등을 클릭했을 경우의 이벤트 함수를 구현해준다. 파라미터는 원하는 값으로 하면 된다.

// 이미 등록된 파일이 있는지 확인
axios.get(`/api/file`, {
    params: {
        file_customer_code: customerKey
    }
}).then(({ data }) => {
    if (data.success) {
        // 파일이 존재할 경우 
    }
})

 

2. 백엔드에서 데이터베이스를 조회하고 있으면 결과값을 반환해주는 라우터를 작성한다.

router.get('/', function (req, res, next) {
    const query = req.query;
    const customer_code = query.file_customer_code

    try {
        File.findOne(
            {
                where: {
                    file_customer_code: customer_code
                }
            }).then((result) => {
                if (result) {
                    res.send({ success: true, result })
                }
                else {
                    res.send({ success: false })
                }
            })
    }
    catch (Err) {
        res.send({ success: false })
    }
})

 

3. 데이터가 존재할 경우 클라이언트의 file State 를 변경해주기 위해 fileReducer를 수정한다.

( 데이터베이스의 값을 불러와 다운로드 할 때 필요한 파라미터들을 클라이언트로 전송 )

function fileReducer(state: any, action: any) {

    switch (action.type) {
        case 'init':
            return initFileState;
        case 'listClick':
            var newState = state;
            Object.keys(newState).map((item: any, index: any) => {
            	if(item === 'file_location') { // 위치로 s3 url을 만들어 다운로드 기능을 구현한다.
                	newState[item] = 'https://  s3 url ' +action.value[item]
                }
                else {
                	newState[item] = action.value[item]
                }
            })
            return newState;
        default:
            return {
                ...state,
                [action.name]: action.value
            }
    }
}

 

4. fileDispatch를 적용해준다. (DB 내용이 state에 잘 들어오게끔)

// 이미 등록된 파일이 있는지 확인
axios.get(`/api/file`, {
    params: {
        file_customer_code: customerKey
    }
}).then(({ data }) => {
    if (data.success) {
        fileDispatch({ type: 'listClick', value: data.result })
    }
})

 

 

이 파일의 key 값인 s3에 저장된 경로가 클라이언트로 넘어오면 클라이언트쪽에서 파일 다운로드 하는 로직을 구현할 수 있다.

 

  1. 리스트 항목같은 데이터를 조회하고자 하는 버튼을 클릭했을 때 해당 데이터에 맞는 파일이 존재하는지 데이터베이스를 조회한다.
  2. 데이터베이스에 없으면 그냥 패스. 만약 있을 경우 데이터베이스에 저장된 내용 중 file_location( s3 file key )를 클라이언트로 가져온다.
  3. 클라이언트에서 s3에 접속하여 다운로드를 한다.
728x90
반응형
728x90
반응형

전 포스트에서 클라이언트에서 백엔드에 데이터를 전송하는 것 까지 구현해보았다.

 

이번 포스트에서는 백엔드에서 s3에 접근하여 파일을 업로드 하는 것까지 구현해보겠다.

 

1. .env 파일을 만들어준다. ( 전에 버킷 생성할 때 key ID 와 secret key 필요 )

S3_ACCESS_KEY='액세스 키'
S3_SECRET_ACCESS_KEY='비밀 액세스 키'
S3_REGION='리전'
S3_BUCKET_NAME='버킷이름'

 

2. 서버 파일에 필요한 것들을 선언하고 라우터를 만들어준다. ( 서버 파일에 body-parser 필요 )

file.js

const multer = require('multer');
const aws = require('aws-sdk');
require('dotenv').config();

const s3 = new aws.S3({
    accessKeyId: process.env.S3_ACCESS_KEY,
    secretAccessKey: process.env.S3_SECRET_ACCESS_KEY,
    region: process.env.S3_REGION,
});

let upload = multer({
    limits: { fileSize: 5 * 1024 * 1024 } // 용량 제한
});


router.post("/upload", upload.array("file_object"), function (req, res, next) {
    try {

        var base64data = new Buffer(req.files[0].buffer, 'binary');

        const params = {
            Bucket: process.env.S3_BUCKET_NAME,
            Key: 'sample.png', // file name that you want to save in s3 bucket
            Body: base64data,
            ACL: "public-read",
            ContentType: "image/png"
        }

        s3.upload(params, (err, data) => {
            if (err) {
                console.log("err : ", err)
                res.send({ success: false });
            }
            else {
                console.log("data : ", data)
                res.send({ success: true, result: data })
            }
        });

    }
    catch (ERR) {
        console.log("ERR : ", ERR)
        res.send({ success: false })
    }

});



module.exports = router;

 

params의 key 부분에서 원하는 파일의 이름과 경로를 지정해줄 수 있다.

 

 

추가로 데이터베이스에 파일에 관한 내용을 추가하여 나중에 불러올 때 편하도록 설계하였다.

s3.upload(params, (err, data) => {
            if (err) {
                console.log("err : ", err)
                res.send({ success: false });
            }
            else {
                File.findOne(
                    {
                        where: {
                            file_customer_code: customer_code,
                        }
                    }).then((result) => {
                        if (result) {
                            File.update({
                                file_url: req.body.file_url,
                                file_location: key
                            }, {
                                where: {
                                    file_customer_code: customer_code,
                                }
                            })
                                .then((result) => {
                                    res.send({ success: true, result: data })
                                })

                        }
                        else {
                            File.create({
                                file_customer_code: customer_code,
                                file_url: req.body.file_url,
                                file_location: key
                            })
                                .then((result) => {
                                    res.send({ success: true, result: data })
                                })

                        }
                    })

            }
        });

 

나중에 파일이 존재하는지 확인할 때 데이터베이스 먼저 확인하고 파일에 접근할 수 있도록 했다.

 

  1. fileState, fileDispatch 설정
  2. input을 만들고 파일 선택 후 state에 담기도록 함.
  3. 담긴 데이터들을 formData로 만들어서 백엔드에 전송
  4. 백엔드에서 해당 파일을 s3에 저장함.
  5. 데이터베이스에 저장된 파일에 대한 내용 (경로 , 고유 키값 등)을 저장함

 

@@ 참고로 파일을 삭제하는 메소드는 deleteObject 이다.

ex)

s3.deleteObject({ Bucket: process.env.S3_BUCKET_NAME, Key: result.file_location }, (err, data) => {
    if (err) {
        console.log("err : ", err)
    }
})

 

728x90
반응형
728x90
반응형

1. aws에 접속하여 IAM 사용자를 생성한다

 

 

2. 권한으로 AmazonS3FullAccess를 할당해준다.

 

3. 해당 Access key ID, Secret access key를 알고 있어야 한다.

 

4. aws s3 화면에 접속해서 버킷을 만들어준다.

 

 

5. 퍼블릭 액세스 차단을 해제하고 만들어준다. (차후에 필요하면 수정 가능)

추가로 버킷정책과 CORS까지 설정해주자.

버킷정책

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "PublicListGet",
            "Effect": "Allow",
            "Principal": "*",
            "Action": [
                "s3:List*",
                "s3:Get*"
            ],
            "Resource": [
                "arn:aws:s3:::00nomubucket1",
                "arn:aws:s3:::00nomubucket1/*"
            ]
        }
    ]
}

 

CORS

[
    {
        "AllowedHeaders": [
            "*"
        ],
        "AllowedMethods": [
            "PUT",
            "POST",
            "DELETE",
            "GET"
        ],
        "AllowedOrigins": [
            "*"
        ],
        "ExposeHeaders": []
    }
]

 

 

이제 버킷까지 만들었으니 node.js 로 파일 입출력 하는 로직을 구성해보자.

 

6. node.js 프로젝트에 필요한 패키지를 설치해준다.

npm i multer aws-sdk --save

 

7. 클라이언트에서 파일 업로드에 필요한 state를 만들어준다.

(file_location은 나중에 받아올 데이터입니다.)

    // ----- 파일 상태값 -----
    const initFileState : any = {
        file_object: "",
        file_url: "",
        file_location: ""
    }

    function fileReducer(state: any, action: any) {

        switch (action.type) {
            default:
                return {
                    ...state,
                    [action.name]: action.value
                }
        }
    }

    const [fileState, fileDispatch] = useReducer(fileReducer, initFileState)

 

8. 클라이언트에 file input을 만들어준다 ( Next.js Mui 사용했으나 일반 input 태그도 가능합니다 )

<WrapperDiv
    kindOf={`input`}
>
    <TextField
        label={`사업자등록증`}
        type='file'
        color='primary'
        size='small'
        InputLabelProps={{
            shrink: true,
        }}
        name={`file_object`}
        onChange={(e: any) => {
            let reader = new FileReader();
            let file = e.target.files[0];
            reader.onloadend = () => {
                fileDispatch({ name: e.target.name, value: file });
                fileDispatch({ name: 'file_url', value: reader.result });
            };
            reader.readAsDataURL(file);
        }}
    />
</WrapperDiv>
{fileState.file_url !== '' &&
    <WrapperDiv>
        <img style={{ width: "100%", height: "100%" }} src={fileState.file_url} />
    </WrapperDiv>
}

 

9. 대충 이런 모습을 볼 수 있다. ( 스타일은 신경쓰지 않아도 됩니다. 아래는 사진 미리보기  )

10. 백엔드에 데이터를 전송하는 로직을 구성해준다. ( 저장하기 버튼 클릭 시 )

const formData = new FormData();

formData.append("file_customer_code", infoState.customer_code); // 파일의 고유 코드 (임의로 지정 가능)
formData.append("file_object", fileState.file_object);
formData.append("file_url", fileState.file_url);

const config = {
    headers: {
        "content-type": "multipart/form-data",
    },
};

axios.post('/api/fileupload', formData, config)
    .then(({ data }) => {
        if (data.success) {
            alert('파일 업로드에 성공하였습니다.')
        }
        else {
            alert('파일 업로드에 실패하였습니다.')
        }
    })

 

클라이언트에서 파일을 업로드하고 백엔드로 데이터를 전송하였다. 다음 포스트에서는 백엔드에서의 파일 저장을 살펴보겠다.

 

728x90
반응형
728x90
반응형

1. 설치

$npm install --save multer

 

2. multer 가져오기 ( 조건문에 따라 파일의 로컬 저장 위치를 변환시킬 수 있음)

const fs = require("fs");
const bodyParser = require("body-parser");
const path = require("path");
router.use("/image_file", express.static("./upload")); // 사용자가 접근할 수 있게 공유
const multer = require("multer");

const uploadCustomer = multer({
    storage: multer.diskStorage({
        destination(req, file, cb) {
            if (!fs.existsSync("../client/public/upload")) {
                fs.mkdirSync("../client/public/upload");
            }

            if (!fs.existsSync("../client/public/upload/customer")) {
                fs.mkdirSync("../client/public/upload/customer");
            }

            if (!fs.existsSync("../client/public/upload/customer/" + file.originalname.split('-')[0])) { // 경로가 존재하지 않을 때
                fs.mkdirSync("../client/public/upload/customer/" + file.originalname.split('-')[0]);

            } else {
                fs.rmdirSync("../client/public/upload/customer/" + file.originalname.split('-')[0], { recursive: true, force: true }); // 기존폴더 및 하위 자료 삭제 
                fs.mkdirSync("../client/public/upload/customer/" + file.originalname.split('-')[0]); // 폴더 생성 
            }
            cb(null, "../client/public/upload/customer/" + file.originalname.split('-')[0]); // 이미지 생성

        },
        filename(req, file, cb) {

            const ext = path.extname(file.originalname);
            cb(null, file.originalname.split('-')[1]);
        },
    }),
    limits: { fileSize: 5 * 1024 * 1024 },
});

 

 

3. upload.single()

router.post('/', upload.single('image'), (req, res) => {
 
    console.log(req.file); 
  // 클라이언트에서 넘어온 파일에 대한 정보가 req.file에 FILE 객체로 저장되어 있습니다. 

})


출처: https://juhi.tistory.com/10 [주하히의 기술 블로그]

 

4. upload.array()

router.post('/', upload.array('photos', 4), (req, res) => { 

console.log(req.files);
console.log(req.files[0]); // 파일의 인덱스로 접근

// 위 single에서와 다르게 req.file이 아닌 req.files에로 넘어옵니다.

})

출처: https://juhi.tistory.com/10 [주하히의 기술 블로그]



// 미발송 근로계약서 이미지 및 서명, 데이터 요청
router.post("/sendLabor", uploadCustomer.array("image_file"), function (req, res, next) {
    const laborCode = req.body.laborCode;

    const state = JSON.parse(req.body.state);
    const 기타입력정보 = state.기타입력정보;
    const 근로자동의 = state.근로자동의;
    const 메인서명 = state.메인서명;

    console.log("req : ", req.files);



    const update_column = [
        "기타입력정보=JSON_OBJECT('근무장소', '" + 기타입력정보.근무장소 + "','담당업무', '" + 기타입력정보.담당업무 + "','주휴요일', '" + 기타입력정보.주휴요일 + "')",
        "근로자동의=JSON_OBJECT('동의여부_1', '" + 근로자동의.동의여부_1 + "','동의여부_2', '" + 근로자동의.동의여부_2 + "','동의여부_3', '" + 근로자동의.동의여부_3 + "','동의여부_4', '" + 근로자동의.동의여부_4 + "','동의여부_5', '" + 근로자동의.동의여부_5 + "','동의여부_6', '" + 근로자동의.동의여부_6 + "','동의여부_7', '" + 근로자동의.동의여부_7 + "','동의여부_8', '" + 근로자동의.동의여부_8 + "')",
        "메인서명=JSON_OBJECT('근로계약서_동의', '" + 메인서명.근로계약서_동의 + "','개인정보_동의', '" + 메인서명.개인정보_동의 + "','사용자_서명', '" + 메인서명.사용자_서명 + "', '근로자_서명', '" + 메인서명.근로자_서명 + "','사용자_서명날짜', '" + 메인서명.사용자_서명날짜 + "','근로자_서명날짜', '" + 메인서명.근로자_서명날짜 + "','사용자서명경로', '" + 'upload/customer/' + laborCode + '/' + "customer.png" + "','근로자서명경로', '" + 메인서명.근로자서명경로 + "')",
    ]

    console.log(update_column);

    const update_data_query = "UPDATE LABOR SET  " + update_column + " WHERE labor_code ='" + laborCode + "'";

    db.sequelize.query(
        update_data_query,
        {
            type: QueryTypes.UPDATE
        }
    ).then((result) => {

        return res.send({ success: true, result });

    })
})

 

5. upload.fields()

router.post('/',
	upload.fields([
    	{ name: 'mainImage', maxCount: 1 },
        { name: 'subImages', maxCount: 5 } ]),
        (req, res) => {
        	console.log(req.files);
            console.log(req.files['접근하려는 fieldname']);
})

출처: https://juhi.tistory.com/10 [주하히의 기술 블로그]
728x90
반응형
728x90
반응형

location.href 메소드를 이용해서 아주 간단하게 전화걸기 기능을 구현할 수 있다.

 

1. 전화걸기

function phoneCall(phoneNumber) {
  location.href = "tel:" + num;
}

phoneCall("01011112222");

 

2. 영상전화걸기 

function phoneCall(phoneNumber) {
  location.href = "tel-av:" + num;
}

phoneCall("01011112222");

 

3. 문자 보내기

function phoneCall(phoneNumber) {
  location.href = "sms:" + num;
}

phoneCall("01011112222");

 

4. 메일 보내기

function phoneCall(phoneNumber) {
  location.href = "mailto:" + num;
}

phoneCall("01011112222");

 

 

이것들은 a 태그 또는 버튼태그로도 사용할 수 있다.

<button onclick="document.location.href='tel:010-1234-5678'"> 

<a href="tel:010-1234-5678">010-1234-5678로 전화걸기</a> </div>
728x90
반응형
728x90
반응형

특정 사이트에서 로그인을 한 뒤 원하는 데이터를 얻어야 될 경우가 생겼다.

api 사이트를 이용할 경우 편하긴 하지만 트랜젝션이 발생 할 때마다의 비용이 발생하기 때문에 

node.js 에서 사용할 수 있는 puppeteer 를 경험해보기로 했다.

 

1. 필요한 npm 패키지를 설치한다.

$npm i puppeteer

 

2. 함수를 구현한다.

const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch(); // puppeteer 시작
  const page = await browser.newPage(); // 브라우저 실행
  await page.goto('https://www.google.com'); // 해당 페이지로 이동
  // other actions...
  await browser.close();  // 브라우저 종료
})();

 

이러한 함수를 만들어두고 백엔드에서 api 요청이 들어올 때 원하는 사이트에 접속 후 원하는 데이터를 가져올 수 있게끔 구현하면 된다.

 

예시)

var puppeteer = require('puppeteer');

(async () => {

    const browser = await puppeteer.launch();
    const page = await browser.newPage();
    const hisnet_id = '히즈넷 아이디';
    const hisnet_pw = '히즈넷 비밀번호';

    //페이지로 가라
    await page.goto('https://hisnet.handong.edu/login/login.php');

    //아이디랑 비밀번호 란에 값을 넣어라
    await page.evaluate((id, pw) => {
    document.querySelector('input[name="id"]').value = id;
    document.querySelector('input[name="password"]').value = pw;
    }, hisnet_id, hisnet_pw);

    //로그인 버튼을 클릭해라
    await page.click('input[src="/2012_images/intro/btn_login.gif"]');

    //로그인 화면이 전환될 때까지 .5초만 기다려라
    await page.waitFor(500);

    //로그인 실패시(화면 전환 실패시)
    if(page.url() === 'https://hisnet.handong.edu/login/_login.php'){
        student_id = 'nope';
        name = 'nope';
    }
    //로그인 성공시
    else{
        //학사 페이지로 가서
        await page.goto('https://hisnet.handong.edu/haksa/hakjuk/HHAK110M.php');
        //학번을 가져오고
        const element1 = await page.$('input[name="hakbun"]');
        student_id = await page.evaluate(element1 => element1.value, element1);
        //이름을 가져와라
        const element2 = await page.$('td[width="240"]');
        name = await page.evaluate(element2 => element2.textContent, element2);
    }
    //브라우저 꺼라
    await browser.close();        
})();

 

 

참조

https://zoomkoding.github.io/web/nodejs/histime/2019/01/24/crawler.html

 

줌코딩의 코딩일기

Zoom in Coding from the Basic.

zoomkoding.github.io

 

728x90
반응형
728x90
반응형

 

var originText = "This is example text.";
console.log("Original : ", originText);

// Base64 Encoding
base64EncodedText = Buffer.from(originText, "utf8").toString('base64');
console.log("Base64 Encoded Text : ", base64EncodedText);

// Base64 Decoding
base64DecodedText = Buffer.from(base64EncodedText, "base64").toString('utf8');
console.log("Base64 Decoded Text : ", base64DecodedText);

 

728x90
반응형
728x90
반응형

CentOS

$ sudo yum install net-tools

 

Ubuntu

$ sudo apt-get install net-tools

 

이후에 명령어 실행

$ sudo netstat -tnlp
728x90
반응형

+ Recent posts