728x90

사용자 기반 서비스를 만들다보니 Textarea에서 라인별 글자수 제한 및 총 라인 제한을 해야할 일이 생겼다

아래와 같은 코드로 해당 문제를 처리하였다.

 

필요한 사람들을 위해 공유한다.

const textareaLimitWordAndRow = (text, limitWordPerline, limeLines) => {
  let lines = text.split('\n');
  const charlimit = limitWordPerline; // 글자수 제한

  for (let i = 0; i < lines.length; i++) {
    if (lines[i].length <= charlimit) continue;
    let j = 0;
    let space = charlimit;
    while (j++ <= charlimit) {
      if (lines[i].charAt(j) === ' ') space = j;
    }
    
    lines[i + 1] = lines[i].substring(space + 1) + (lines[i + 1] || '');
    lines[i] = lines[i].substring(0, space);
  }

  return lines.slice(0, limeLines).join('\n');
};

 

 

728x90
반응형
728x90

프로젝트를 진행하다보면

목적은 다양하지만 결국 엑셀 출력기능을 사용하게된다.

어드민, 거래처 요구사항 등...

(대시보드가 있지만, 회사내적으로 파일로 관리를 해야한다나..)

 

그래서 이래저래 유용한 엑셀 출력기능을 알아보려고한다.

사용법도 간단하며 매우 유용한 기능이니 필요할때 한번씩 열람해서 보도록하자.

 

먼저, 아래와 같이 라이브러리를 설치하자.

npm i xlsx

(참고로, 해당 라이브러리는 브라우저와 nodejs에서 모두 사용가능하다.)

 

자, 이제 데이터를 엑셀로 출력해보자.

import XLSX from 'xlsx';

const data = [
  {
    index: 1,
    name: 'apple',
    price: '1,200'
  },
  {
    index: 2,
    name: 'grape',
    price: '3,500'
  },
  {
    index: 3,
    name: 'strawberry',
    price: '5,500'
  }
];

const exportDataToXlsx = ({ data, filename }) => {
  const worksheet = XLSX.utils.json_to_sheet(data); // excel sheet하단의 worksheet에 해당
  const new_workbook = XLSX.utils.book_new(); // excel 파일에 해당
  
  XLSX.utils.book_append_sheet(new_workbook, worksheet, 'SheetJS'); // excelsheet를 excel파일에 넣음
  XLSX.writeFile(new_workbook, filename + '.xlsx');
}

 

이와 같이 매우 짧고 간단한 코드로 간단히 excel파일을 출력할 수 있다.

응용한다면, data의 타입이나 기간등에 따라 sheet를 나누어 출력하거 파일을 구분할수도있을것 같다.

 

그 또한 매우 간단하지만, 비개발자 입장에서는 매우 고급기술처럼 보일수있다.

잘 알아뒀다가 고급기술인척 해보도록하자.

728x90
반응형
728x90

[1탄] https://honeystorage.tistory.com/188?category=748845

[2탄] https://honeystorage.tistory.com/189?category=748845

 

1,2 탄에 이어서 포스팅을 진행해보겠습니다.

앞서 1,2탄에서는 선행 작업과 템플릿에 대해서 알아봤습니다.

 

이번에는 실제 예제코드를 통해 API요청 테스트를 진행해보도록 하겠습니다.

그 전에, 템플릿에 대해서 조금만 추가 설명을 하고자 합니다.

 

 

알림톡을 보낼때 내용이 한줄작성인 경우는 굉장히 드물것입니다.

그런데 줄바꿈을 하다보면, API요청 과정에서 또 오류를 만나게 되고는 합니다.

보통, javascript에서 일반 문자열과 변수를 섞어서 사용할때 "백틱"이라 불리는 그레이브엑센트를 쓰는데요.

만약에,

// 템플릿
오늘 점심메뉴는 #{ somethingfood }이었습니다.
정말 맛있었습니다.

------------------------------
// 잘못된 예제
const message = `
  오늘 점심 메뉴는 ${food}이었습니다.
  정말 맛있었습니다.
`;


// 잘못된 예제
const message = `
오늘 점심 메뉴는 ${food}이었습니다.
정말 맛있었습니다.
`;

// 올바른 예제
const message = `오늘 점심 메뉴는 ${food}이었습니다.
정말 맛있었습니다.`

 

템플릿에 대해서 감이 오시나요?

템플릿과 소스코드는 변수 부분을 제외하고는 정확하게 내용이 같아야지만 올바른 요청으로 인정됩니다.

자꾸만 에러가 난다면 꼭 템플릿과 코드를 옆에 두고 비교해보시기 바랍니다.

 

 

자, 본격적으로 소스코드 예제를 진행해보도록 하겠습니다.

NCP를 처음 사용하신다면 흔히 막히는 부분이 암호화 서명 관련 섹션입니다.

DOCS에 친절히 나와있지만, nodejs관련해서는 다소 불친절하기 때문이죠.

 

암호화 서명을 위한 예제코드를 살펴보겠습니다.

function makeSignature() {
  var space = ' '; // one space
  var newLine = '\n'; // new line
  var method = 'POST'; // method
  var timestamp = Date.now().toString(); // current timestamp (epoch)
  var accessKey = process.env.NAVER_ACCESSKEY; // access key id
  var secretKey = process.env.NAVER_SECRETKEY; // secret key
  const url2 = `/alimtalk/v2/services/${process.env.NAVER_CHANNEL_ID}/messages`;
  let message = [];
  let hmac = crypto.createHmac('sha256', secretKey);

  message.push(method);
  message.push(space);
  message.push(url2);
  message.push(newLine);
  message.push(timestamp);
  message.push(newLine);
  message.push(accessKey);
  return hmac.update(message.join('')).digest('base64').toString();
}

여기서 주로 살펴볼 부분은,

crypto / NAVER_ACCESSKEY / NAVER_SECRETEKY / NAVER_CHANNEL_ID 입니다.

 

1. crypto는 nodejs의 내장모듈은 crypto를 의미합니다.

const crypto = require('crypto'); 해주시면 됩니다.

 

2. NAVER_ACCESSKEY는 1탄에서 알려드린 "계정관리"에서 생성한 "API인증키"를 말합니다.

 

3.  NAVER_SCRETKEY "API인증키" 옆에 보이는 "Secret key"입니다.

 

4. NAVER_CHANNEL_ID는 NCP의 easy notification에 등록한 프로젝트에 보면 Biz message가 있죠? 열쇠모양을 누르면 해당 서비스의 id를 확인할 수 있습니다.

 

 

거의 다 됐습니다.

마지막으로 4탄에서는, 인증요청 작업을 API문서와 비교 작성해보며 마무리해보도록 하겠습니다.

728x90
반응형
728x90

[1탄] https://honeystorage.tistory.com/188?category=748845

[2탄] https://honeystorage.tistory.com/189?category=748845

[3탄] https://honeystorage.tistory.com/190

 

1, 2, 3탄에 이어서 포스팅을 진행해보겠습니다.

앞서 1, 2, 3탄에서는 선행 작업과 템플릿, 암호화서명에 대해서 알아봤습니다.

 

자, 이제 마무리입니다.

API연동을 진행함에 있어 Docs를 안볼수는 없겠죠?

https://apidocs.ncloud.com/ko/ai-application-service/sens/alimtalk_v2/

 

우리가  참고할 파트는 메시지 발송입니다.

단일 메시지 발송과 멀티 메시지 발송의 차이는 수신자를 한명만 두느냐, 여러명 두느냐 정도의 차이밖에 없습니다.

따라서, 우리는 단일 메시지 발송에 포커스하여 진행해보도록 하겠습니다.

 

"Mandatory"라고 표기된 값들이 필수값인데요.

그외의 것들은 API요청때 없어도 되는것들입니다. 우리는 "Mandatory"만 파라미터로해서 요청을 날려보도록 하겠습니다.

 

순서는 다음과 같습니다.

1. 암호화서명 함수를 만든다. (3탄에서 진행했습니다.)

2. template코드에 따라 적절한 parameter를 생성해주는 함수를 만든다.

3. 요청한다.

 

"Mandatory" 항목중에 templateCode가 있습니다.

해당 templateCode를 통해 우리의 요청이 어떤 템플릿에 대응되는것인지를 알려주는것입니다.

 

서비스를 운영하면서 템플릿을 한개만 쓰는경우는 없기때문에, 

if문이나 switch문 등을 통해 원하는 parameter를 세팅해서 리턴해주는 함수를 만들도록 하겠습니다.

 

다음은 예시입니다.

const getRequestParams = ({ type, to, data }) => {
  if (type === 'food') {
    return {
      templateCode: type,
      plusFriendId: process.env.MyChannelId,
      messages: [
        {
          to,
          content: `내가 만든 ${ data.food }는 맛있습니다.`,
          buttons: [
            {
              type: 'WL',
              name: '레시피 확인',
              linkMobile: 'https://food.tech.kr',
              linkPC: 'https://food.tech.kr',
            }
          ]
        }
      ]
    }
  }
  
  if (type === 'car') {
    return {
      ...
    }
  }
  
  ...
}

간단히 이런식으로 작성해 나갈수 있겠죠?

switch문으로 하면 더 깔끔할수 있겠네요.

 

이제 정말 마지막입니다.

4탄에 걸쳐서 포스팅을 하고있지만, 막상 해보면 1시간도 안걸려 끝날것입니다.

(시행착오를 겪은 제입장에서는 반나절은 썼지만요..)

 

자, 요청을 해봅시다!

const sendKakaoMessage = async ({ templateCode, to, data }) => {
  const params = getRequestParams({ type: templateCode, to, data });
  
  const { data: result } = await axios.post(`https://sens.apigw.ntruss.com/alimtalk/v2/services/${process.env.NAVER_CHANNEL}/messages`, params, {
    headers: {
      'Content-Type': 'application/json; charset=utf-8',
      'x-ncp-apigw-timestamp': Date.now().toString(),
      'x-ncp-iam-access-key': process.env.NAVER_ACCESSKEY,
      'x-ncp-apigw-signature-v2': makeSignature(),
    },
  });
  
  console.log('과연? ', result);
}

 

어떤가요? 잘 되셨나요?

여기까지 따라오느냐 고생많으셨습니다.

유료서비스 안녕, 이제는 무료로 카톡의 좋은 서비스 이용하시기 바랍니다.

--- 수정 ---

미흡한 포스팅으로 혼란을 야기해드렸다면 죄송합니다. ㅜㅜ

제가 언급한 무료라는 것은, 중간에 API서비스 업체를 끼지 않고도 직접 알림톡을 구현함으로써

API업체를 끼는 비용을 무료로 가져갈수 있다는 얘기였습니다.

-----------

 

ps. 오랜만에 정성스러운 포스팅이네요. 반응보고 앞으로 방향성을 갖도록 하겠습니다.

728x90
반응형
728x90

[1탄] https://honeystorage.tistory.com/188?category=748845

1탄에 이어서 포스팅을 진행해보겠습니다.

앞서 1탄에서는 선행 작업들을 진행했는데요.

 

이번에는 템플릿 검수를 위한 노하우를 공유하고자합니다.

템플릿을 제대로 등록하지 않으면

내가 코드 문제로 API 요청에서 에러가 나는건지, 템플릿이 문제가 있어서 에러가 나는건지 헷갈리게되는

늪에 빠지게됩니다.

 

어렵지 않으나, 어디에도 자료가 없어 정리하니 잘 참고하시기 바랍니다.

NCP의 예제를 보면 변수 부분에는 #{}이렇게 처리를 하라고 나옵니다.

 

근데, 실제 데이터에서 "#{ 홍길동 }" 이라고 입력을해야하나 아니면 "홍길동" 이라고만 입력하면 되는건지

헷갈립니다.

또한, 처음 템플릿을 생성하게되면 흔히 겪는 오류가 띄어쓰기 한칸까지도 모두 똑같이 맞춰야하는데 그것을 지키지 않아서 겪게되는 오류입니다.

 

예를들어 설명해보겠습니다.

저에게는 변수 food가 있습니다.

먼저, 템플릿을 다음과 같이 등록합니다.

// 템플릿 예제

// 예제 1번
저의 점심 메뉴는 #{food}이었습니다.

// 예제 2번
저의 점심 메뉴는 #{somethingfood}이었습니다.

// 예제 3번
저의 점심 메뉴는 food이었습니다.

// 예제 4번
저의 점심 메뉴는 somethingfood이었습니다.

여기서, 적절한 템플릿은 1번과 2번입니다.

 

 

자, 그런데 해당 템플릿에 맞게 코드를 작성할때는 어떻게 해야될까요?

1. 저의 점심 메뉴는 ${ food }이었습니다.
2. 저의 점심 메뉴는 #{${food}}이었습니다.
3. 저의 점심 메뉴는 ???

 

다음 포스팅에서 코드 작성을 이어가보도록 하겠습니다.

728x90
반응형
728x90

무료로 카톡알림 보내기 어렵지 않습니다.

여기서 무료라 하면, 메세지 발송해주는 업체를 쓸필요가 없음을 의미합니다~

 

조건이 있습니다.

1. 개발자이거나 회사에 개발자가 있다.

2. 카카오톡 채널(플러스친구, 구 옐로아이디) 계정이 있어야 한다.(이것은 유료입니다)

 

카카오톡의 API를 직접 이용해도 되지만,

이번에는 네이버 NCP의 easy notification - biz message를 이용해보도록 하겠습니다.

 

굳이, 카톡 API를 안쓰고 네이버를 이용한 이유는 단 한가지입니다.

네이버가 더 쉽게 API를 풀어서 기술해뒀기 때문입니다.

그만큼 들어가는 시간 비용이 줄어들겠죠?

 

 

선행해야할 작업은 카카오톡에서 카카오채널을 개설해야합니다.

두번째는 네이버 NCP(Naver Cloud Platform)에 가입해야합니다. (네이버 연동 가능)

이 작업이 완료되었다는 가정하에 개발관련해서 포스팅을 시작하도록 하겠습니다.

 

카톡알림을 해주는 서비스인 "biz-message"를 이용하기위해서는

https://console.ncloud.com/sens/kakao-talk-channel <<< 이 링크로 이동합니다.

 

여기서 API사용을 위한 몇가지 작업을 먼저 진행하겠습니다.

1. 좌측 네비게이션에서 easy notification - project에서 신규 프로젝트를 생성해줍니다.

 > 이 후, 채널을 생성하고 나면 여기서 해당 서비스의 id및 secret key등을 확인할 수 있습니다.

 

2. 채널등록 - 카카오톡 채널이 없다면 바로 옆에 "채널생성" 버튼도 있으니 여기서 진행하시기 바랍니다.

 

3. API사용을 위해 키를 생성해야합니다.

 > NCP 우측상단의 내 프로필 아이콘을 클릭하면 "계정설정"이 있습니다.

 > 계정설정에서 로그인을 진행한 뒤, 계정관리 - 인증키 관리로 이동합니다.

 > 여기서 신규 API키를 생성합니다.

 

 

이것으로 API사용을 위한 코드를 치기전 진행할 작업을 모두 마쳤습니다.

다음은 템플릿을 등록하게 되는데요.

템플릿은 어떤 내용을 보내는지, 어떤 버튼을 붙일것인지 등을 미리 정한 것을 의미합니다.

템플릿에 대칭되는 코드를 통해 템플릿을 쉽게 관리하고 사용할 수 있습니다.

 

 

다음 포스팅에서, 템플릿 등록 노하우 및 검수등에 소요되는 기간 등을 알려드리도록 하겠습니다.

https://honeystorage.tistory.com/189

 

728x90
반응형
728x90

현 직장은 비디오를 기반으로하는 플랫폼을 운영중이다.

따라서, 비디오 관련기술을 차례로 접해보는중인데

플레이어와 관련하여서는 youtube, wistia, vimeo등을 써봤고 (wistia를 선택)

촬영관련해서는 RecordRTC를 적용중이다.

 

국내에는 자료가 거의 0에 가까운 RecordRTC에 대해서 간단히 소개해본다.

 

RecordRTC는 웹에서 카메라/비디오를 운용하기 위한 라이브러리다.

즉, 웹캠을 지원하는 라이브러리이다.

 

왜 이것이 필요할까?

html5에서 input태그를 잘 활용하면 카메라에 접근이 가능하다.

다만, 그 범위가 플랫폼에 따라 제한된다.

 

예를들어,

<input id='camera-yap' type='file' accept='video/mp4' capture='camera' />

이, 간단한 한줄로도 기본적으로 카메라에 접근할 수 있다. (클릭하여 카메라를 구동시킬 수 있음)

운영체제에 따라 먹통이 되고는 한다.

서비스를 그런식으로 만들수는 없다. 모두에게 평등한 서비스를 제공하는것을 원칙으로해야한다.

 

그래서 알아보다보니 RecordRTC라는 라이브러리가 있었다.

어렵지는않지만, 예제가 없어 초보자에게는 간단하지 않다.

 

 

몇일내로, 예제소스를 '이곳'에 업데이트 하도록 하겠다.

 

===== 업데이트(20. 04. 22) =====

1. RecordRTC를 사용하기위해 설치를 하도록하자

https://www.npmjs.com/package/recordrtc

 

recordrtc

RecordRTC is a server-less (entire client-side) JavaScript library that can be used to record WebRTC audio/video media streams. It supports cross-browser audio/video recording.

www.npmjs.com

2. 예제를 참고해 구현해보자.

import React from 'react';
import RecordRTC from 'recordrtc';

class RecordRTCTest extends React.Component {
  state={
    recorder: null,
    video: null,
  }
  
  onRecord = () => {
    navigator
      .mediaDevices
      .getUserMedia({ audio: true, video: true })
      .then(function(camera) {
        this.readyCamera(camera);
      })
      .catch(function(error) {
        console.error(error);
      });
  }
  
  readyCamera = (camera) => {
    const video = document.getElementById('test-video');
    video.muted = false;
    video.volume = 0;
    video.srcObject = camera;
    
    const recorder = new RecordRTC(camera, {
      type: 'video',
    });
    
    recorder.startRecoding();
    recorder.camera = camera;
    
    this.setState({ recorder });
  }

  onStop = () => {
    const { recorder } = this.state;
    const video = document.getElementById('test-video');
    
    video.src = video.srcObject = null;
    video.muted = false;
    video.volume = 1;
    video.src = URL.createObjectUrl(recorder.getBlob);
    
    // 카메라 정지
    recorder.camera.stop();
    
    // 서버 저장 등을 위해 영상object 데이터 저장
    this.keepVideo(recorder);
    
    this.setState({ recorder: null });
  }
  
  keepVideo = (data) => {
    this.setState({
      video: data.blob,
      src: URL.createObjecetUrl(data.getBlob()),
    });
  }

  render() {
    return (
      <div className='video-test-container'>
        {
          recorder ?
          <p>녹화중이에요~~</p>
        }
        <video
          id='test-video'
          autoPlay
          playsInline
          controls
        />
        <button onClick={this.onRecord}>촬영하기<button/>
        <button onClick={this.onStop}>중지하기<button/>
      </div>
    )
  }
}

 

기본적인 예제소스입니다.

테스트는 해보지는 않았으며, 실제 구현된 코드기반으로 기본 틀만 잡아둔 소스입니다.

 

 

728x90
반응형
728x90

Html, JS를 가지고

 

이미지 파일을 불러온 후, 미리보기를 구현하는 아주 간단한 방식을 소개해본다.

 

window객체에 내장되어있는 FileReader를 이용한 것이다.

 

먼저, input file에 onChange이벤트리스너를 붙여준다.

 

const oFReader = new FileReader();
oFReader.readAsDataURL(ev.target.files[0]);
oFReader.onload = ev => {
    document.querySelector('#uploadImage').src = ev.target.result;
};

위와 같은 코드로, 이미지 미리보기를 간단히 구현해볼 수 있다.

uploadImage는 해당 이미지 태그이다.

 

(예, <img id='uploadImage' alt='profile' />)

 

 

 


웹사이트 개발 / 홈페이지 제작 / android앱 개발 / ios 앱 개발 / server / client / aws / fullstack / buisness partner / 외주 / 용역

https://open.kakao.com/o/sNETgUJb

http://self-made.cloud

 

 

 

728x90
반응형
728x90

momentjs 언어 포맷을 변경할 수 있다.

 

관련해 많은 포스팅들이 있다.

 

그런데, 번거로운 작업없이 요일만 한글화 한다던지, 년 월 등만 한글화 하고 싶을때가 있다.

 

그럴때는 moment의 내장함수를 이용하면 가능하다.

 

updateLocale이다.

 

예를들어, 요일을 업데이트한다면,

import moment 아래에

moment.updateLocale('kr', {
    weekdaysShort: ['일', '월', '화', '수', '목', '금', '토'],
});

과 같이 설정해주고

moment().locale('kr).format('ddd') 해보면 한글화 된걸 확인 할 수 있다.

 

 

 


 

웹사이트 개발 / 홈페이지 제작 / android앱 개발 / ios 앱 개발 / server / client / aws / fullstack / buisness partner / 외주 / 용역

https://open.kakao.com/o/sNETgUJb

http://self-made.cloud

 

 

 

728x90
반응형
728x90

File upload는 대부분의 앱/웹에서 사용되는 아주 기본적인 기능

 

클라이언트 사이드에서의 처리는 생각보다 간단하다.

 

1. input type='file'로 태그를 생성하고,

 

2. onChange 이벤트리스너를 통해 ev.target.files[0]으로 선택된 파일을 읽어와 변수에 담는다.

 

3. 서버로 보내준다. 보내줄때는 formdata에 넣어보내줘야한다.

const formdata = new Formdata();

formdata.append('photo', file);

여기서 photo는 서버에서 해당 요청을 받을때 사용하게된다.

(예를들어, multer를 이용한 미들웨어에서 사용한다면 photo.upload(photo);

이때는 헤더설정도 해줘야한다. 

const config = { headers: { 'Content-Type': 'multipart/form-data' } }; << 이렇게

axios.post(url, formdata, config) 와 같이 서버에 요청을 날리면된다.

 

 

 


웹사이트 개발 / 홈페이지 제작 / android앱 개발 / ios 앱 개발 / server / client / aws / fullstack / buisness partner / 외주 / 용역

https://open.kakao.com/o/sNETgUJb

http://self-made.cloud

 

 

 

728x90
반응형