[좌충우돌 개발기] 플랫폼별 QR, 바코드 스캐너 구현기

최근 모바일 카메라가 발달함에 따라 많은 서비스들이 현금지불, 쿠폰 등의 기능에 스캐너를 이용하고 있습니다. 결제분야를 예로 들면 최근 중국내 1위 사업자로 올라선 위챗의 경우 작년 9월 기준 QR 코드 스캔을 이용한 1일 결제량이 10억건에 달한다고 합니다.  최근 중국에서 선풍적인 인기를 얻고 있는 자전거 공유 서비스의 경우 QR 코드를 이용하여 자전거의 잠금 장치를 해제하는 기능도 제공합니다. 이번글에서는 이와 같이 중국에서 가장 인기 있는 인터페이스 수단으로 사용되고 있는 QR 및 바코드 스캔 기능을 3가지 버전(HTML5, React Native, WeChat embeded scanner) 으로 개발하게 된 내용을 공유하려고 합니다.

mobike_qr

중국 자전거 공유 서비스 (Mobike) QR 스캔

먼저 서비스에 대해서 간략히 소개해 드리자면 첫째, 오프라인 매장으로 배송되어온 박스의 송장을 스캔하여 입고내역을 확인하고, 입고된 재품의 재고를 반영합니다. 둘째, 반영된 상품의 tag를 스캔하여 재고내역을 확인하는 심플한 마이크로 서비스 입니다.

스캔기능과 관련된 요구사항으로는

  • QR 코드와 바코드가 모두 스캔이 가능하여야 할 것
  • 코드가 박스에 스티커로 붙어서 오는데 배송 과정 중 약간의 손상(이물질, 긁힘)등이 있어도 잘 스캔이 되어야 할 것

결론 부터 말씀드리자면 전 동일한 기능을 총 3차례 개발한 끝에 만족스러운 스캔성능을 얻을 수 있었습니다. 이제부터 1가지씩 구현했던 방법을 소개해 드리겠습니다.

HTML5 를 이용

첫번째로 선택했던 구현방법은 javascript 라이브러리를 이용한 방식이었습니다. 이유는, web을 이용하면, 개별매장으로의 배포문제도 크게 신경쓰지 않아도 되고, 구현도 javascript를 이용하면 native 방식보다 비교적 쉽게 할 수 있을 것(제가 web 이외의 경험이 없었다는 것이 좀더 정확하다는;;;) 같아서 였습니다.

개발 방향을 정하고 구글을 통해 라이브러리를 검색한 결과 'quaggajs(https://serratus.github.io/quaggaJS/)'를 선택하였습니다. quaggar를 선택한 이유는 일단 QR, 바코드가 모두 스캔이 가능하였고,  github에 스타가 많았으며,  최근까지 코드 유지보수가 되고 있었기 때문입니다.

아래는 quagga 를 이용한 스캐너 코드입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
Quagga.init({
      inputStream: { // 옵션으로 이미 존재하는 사진을 입력받거나, 실시간으로 찍어서 입력받을수 있습니다.
        name: 'Live',
        type: 'LiveStream',
        constraints: {
          width: screen.width - 50,
          height: 180,
          facingMode: 'environment' // or user
        },
        target: document.querySelector('#yourElement')
      },
      locator: {
        patchSize: 'medium',
        halfSample: true
      },
      numOfWorkers: 4,
      decoder: {
        readers: [ 'ean_8_reader', {
          format: 'ean_reader',
        }]
      },
      locate: true
    }, (err) => {
      if (err) {
        return console.log(err)
      }
      Quagga.start()
    })

스캐너 동작과정을 설명드리면  javascript 객체로 option을 init 함수에 전달하면 스캐너를 사용하기 위한 준비가 끝납니다. 나머지는 스캔이후의 동작을 정의해주는 callback 함수를 등록해주는 일입니다.

설명이 간단한 만큼 quagga를 이용한 개발은 생각했던 것처럼 크게 어렵지 않았습니다. 기억해둘만한 이슈라면 디바이스의 카메라를 사용하기 때문에, 서버에 올렸을 때, 꼭 https로 배포가 필요하다는 것이 있었습니다. 또, 바코드 부여방식이 다양한데 스캔하려고 하는 바코드의 타입을 확인해야 한다는 것이었습니다. (제경우는 ean 타입의 바코드 였습니다.)

개발은 그럭저럭 쉽게 넘어갔지만 quagga를 이용해 개발한 스캐너에는 근본적인 결함이 있었는데... 스캔 인식률이 많이 떨어진다는 것이었습니다. 밝은 조명아래에서 카메라에 정확히 잡아줘야만 겨우겨우(라는 표현을 사용한 이유는 그래도 스캔이 안되는 경우가 종종 있었기에..ㅠㅠ) 인식하니... 매장 직원이 창고에 가서 손상이 있을지도 모르는 코드를 스캔하는 일은 매우 어려울것으로 생각되어 다시 개발하기로 하였습니다.

react-native 이용

두번째로 선택한 개발방법은 react-native 를 이용한 방법이 었습니다. 해당 개발방식을 선택한 이유는 인식률을 높이기 위해선 native 모듈을 사용해야 한다는 내부결론이 있었습니다. 하지만, native 경험이 없는 제가 선택할수 있는 선택지는 ionic, react-native 정도였는데. 현재 프로젝트의 다수가 react 로 되어 있었기에 react-native 로 결정하는 것은 매우 자연스러운 선택이었습니다. react-native 로 스캐너를 개발하기 위해서는 추가로 모듈을 설치 하여야 하는데 react-native-barcodescanner 와 react-native-camera 라는 모듈이 있었는데 react-native-barcodescanner는 이미 유지보수가 종료된 프로젝트였으므로 react-native-camera(https://github.com/lwansbrough/react-native-camera)를 선택하여 개발을 진행하였습니다.

아래는 제가 구현한 코드 입니다.

1
2
3
4
5
<Camera onBarCodeRead={this._onBarCodeRead} style={styles.camera}>
    <View style={styles.rectangleContainer}>
        <View style={styles.rectangle} />
    </View>
</Camera>

react 컴포넌트를 가져와 쓰는 것이기 때문에 코드가 매우 간단합니다.(ㅡ,.ㅡ 스캔영역을 표시해주는 css 코드가 제일 많았습니다..)

스캔한 값을 처리하기 위해 onBarCodeRead에 callback 함수를 만들어 등록해주면 되고, View 컴포넌트에 스캔영역을 css를 사용하여 표현해주면 됩니다. react-native 스캐너 구현체는 스캔기능이 굉장히 만족스러웠습니다. 휙 스쳐도 스캔이 되고, 바코드에 손상이 있어도 스캔되고 ...ㅎㅎ (됐다! 하지만...)

이렇게 스캔기능이 만족스러운데 1번 더 개발을 진행한 이유는 배포문제 때문이었습니다. 앱을 사용해야 할 매장이 많은 경우(대륙의 스케일을 상상해보시면...) 모든 매장에 앱을 설치하도록 하는것이 너무 어렵다고 판단 하였습니다. 중국에서 가장 많이 사용하는 WeChat의 기업계정 app을 이용하여 배포하는 것이 가장 좋다는 결론을 내렸습니다.

WeChat 에는 공식계정과 기업계정 서비스가 있는데 두 서비스 모두 web 프로젝트를 web view 형태로 등록하여 WeChat 내에서 전용 브라우저로 접근이 가능합니다. 그외 자세한 내용은 이번 글에서는 줄이고 기회가 되면 다른 글에서 소개해 드리겠습니다.

새로운 요구사항이 생겨서 WeChat 에 대해 조금 더 알아보니 WeChat은 카메라, 녹음, 스캔, 지불 등 WeChat에서 제공하는 많은 기능을 API로 대부분 열어주고 있었습니다.(오! 대박) 또, WeChat의 기업계정 서비스를 이용하면 매장직원의 권한관리도 용이하고, 배포는 WeChat을 통하면 되기 때문에 문제가 되지않았습니다.(이미 8억명이 쓰고 있다니...뭐ㅎㅎ)

그럼 WeChat API를 사용하기 위한 과정을 간략히 설명드리면

  1. 기업계정을 만든다.
  2. web의 기업계정 관리자 화면에서 app을 추가하고 url을 등록한다.
  3. jsapi 를 사용하기 위해 (noncestr, timestamp, accessToken, returnUrl)을 sha1 로 암호화하여 전송.
  4. 3의 결과로 wexin 객체를 초기화하고 사용하고자 하는 api 호출

위와 같습니다.

3번의 과정은 친절하게 document(获取地理位置接口)에서 node, php, java 등의 언어로 예제를 제공해 주고 있습니다. 중문이라는 점이 조금 아쉽지만 번역기를 사용하는 노오력을 들인다면 알아볼수 없을 정도는 아닙니다.(영문 문서가 빨리 나오길 바라며...) 주의 해야할점은 3의 과정을 꼭 API를 사용하고자 하는 page에서 해야 하고, returnUrl에는 query string 또는 spa 를 사용하여 클라이언트 사이드 라우팅할때 생기는 '#' (예> react-router 의 hashHitory) 이 없어야 한다는 것입니다.

코드는 quagga와 비슷한 맥락인데

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
wx.config({
          debug: debugMode,
          appId: wxconfig.appId,
          timestamp: wxconfig.timestamp,
          nonceStr: wxconfig.nonceStr,
          signature: wxconfig.signature,
          jsApiList: apiList
        })
config를 설정하고, 
wx.scanQRCode({
                desc: 'scanQRCode desc',
                needResult: 1, // 默认为0,扫描结果由微信处理,1则直接返回扫描结果,
                scanType: ["qrCode","barCode"], // 可以指定扫二维码还是一维码,默认二者都有
                success: (res) => {
                  // 回调
                }
})

API 를 호출하고, callback을 설정하면 됩니다. WeChat과 동일한 퍼포먼스의 스캐너의 성능은 대충 스치고 지나만 가도 찍힐 정도이니 말이 필요없습니다. 그리고 스캔영역도 위챗과 동일하게 표시되어 css 작업으로 부터 해방될수 있었습니다.(호우!)

아래는 WeChat을 통한 스캔 시연영상입니다.

[video width="544" height="960" mp4="http://www.popit.kr/wp-content/uploads/2017/04/WeChatSight40-1.mp4"][/video]

정리해보면 위에서 html5, react-native, WeChat API 이렇게 3가지 방식으로 구현한 방법을 알아보았습니다. 배포와 성능적인 측면에서 WeChat이 굉장히 만족스러웠지만 WeChat의 경우 기업계정을 만들어야 한다는 제약사항이 존재합니다. 위챗에 기업계정이 없거나, 개별 앱을 배포 해야만 하는 경우 react-native 를 이용한 방식도 스캐너의 성능 적인 측면은 굉장히 만족스러웠기에 고려해볼만한 옵션인 것 같습니다. HTML5 는 제가 부족하여 제대로 사용하지 못하였을 가능성도 있으나 다른 옵션이 존재한면 굳이 추천하고 싶은 방법은 아닙니다.


Popit은 페이스북 댓글만 사용하고 있습니다. 페이스북 로그인 후 글을 보시면 댓글이 나타납니다.