'nodejs'에 해당되는 글 18건

web/node.js

sequelize에서 조인 시 left join이 되지 않을 때 처리하는 방법

sequelize에서 조인을 할때는 include를 사용해서 다음과 같이 한다.


1
2
3
4
5
6
await This.User.findOne({
  attributes: ['id', ['name''userName']],
  include: [
    { model: this.Dept}
  ]
});
cs

물론 기존에 Model을 define할 때 연관관계를 설정을 해놓은 상태여야 하고 이렇게 할 경우에 나는 left join이 아니라 inner join으로 다음과 같이 되었다.


SELECT id, name as userName FROM User u inner join Dept d on u.userId = d.userId;

그래서 검색해서 알아보다보니 required 옵션을 부여하게 되면 정상적으로 left join이 된다고 알게되었다. 

그래서 붙이니 정상적으로 되었다.


1
2
3
4
5
6
await This.User.findOne({
  attributes: ['id', ['name''userName']],
  include: [
    { model: this.Dept, required: false}
  ]
});
cs


별개로 조인을 하려고 하는데 계속 서브쿼리가 만들어진다면 subquery:false 옵션을 주면 문제를 해결 할 수 있다.

web/node.js

sequelize 사용시 테이블 이름 변동없이 고정 Alias 사용방법

sequelize는 마찬가지로 ORM을 사용하다보니 직접적으로 쿼리를 사용하는 것보다 정확하게 알지못하면 역시 개발속도도 늦어지고 문제가 많아지는 단점이 있다.


이번에는 sequelize를 사용하는데 조인할 때 테이블 이름이 갑자기 User에서 Users로 바뀌는 이슈가 발생했다.


이 이슈를 해결하기 위해서 sequelize Document를 검색했고 거기서 freeTableName 옵션을 발견했다.

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
const Bar = sequelize.define('bar', { /* bla */ }, {
  // don't add the timestamp attributes (updatedAt, createdAt)
  timestamps: false,
  // don't delete database entries but set the newly added attribute deletedAt
  // to the current date (when deletion was done). paranoid will only work if
  // timestamps are enabled
  paranoid: true,
 
  // don't use camelcase for automatically added attributes but underscore style
  // so updatedAt will be updated_at
  underscored: true,
  // disable the modification of table names; By default, sequelize will automatically
  // transform all passed model names (first parameter of define) into plural.
  // if you don't want that, set the following
  freezeTableName: true,
 
  // define the table's name
  tableName: 'my_very_custom_table_name',
  // Enable optimistic locking.  When enabled, sequelize will add a version count attribute
  // to the model and throw an OptimisticLockingError error when stale instances are saved.
  // Set to true or a string with the attribute name you want to use to enable.
  version: true
})
cs

설명 그대로 이름이 plural로 바뀌는 것을 방지 하고 singular로 사용할 수 있게 해주는 옵션이다.



web/node.js

node js에서 stream pipe 사용시 에러 처리 방법

node js에서 데이터를 stream을 사용하여 처리하고 pipe를 사용해서 계속해서 stream을 가지고 작업을 이어나갈 수 있다. 그런데 pipe를 통해서 작업을 진행하다 보니까 중간에 오류가 발생했을 때 try / catch 로는 정상적으로 처리하지 못하는 경우가 발생했다. 

나에 경우에는 에러가 발생했을 때 try / catch에서 잡히지 않아서 프로그램이 Unhandled Promise Rejections를 출력 하며 죽어버렸다.

그 예는 다음과 같이 request를 통해서 받은 이미지를 sharp 라이브러리를 통해서 이미지 크기를 변경하려고 할 때 발생했다.

1
try {
await request('https://image.toast.com/aaaaab/ticketlink/TKL_3/ion_main08061242.jpg').pipe(transformer).pipe(writeStream);
} catch (e) {
console.error(e);
}
cs


그래서 이를 처리하기 위해서 알아봤는데 각 파이프라인에서 발생하는 에러를 처리하기 위해서는 try/catch로만 잡을 수가 없다. 그래서 이를 해결하기 위해서 각 파이프 앞단에서 error 이벤트를 잡는 설정을 해줘야한다.


1
2
3
4
5
6
7
8
// 파일로 쓰기
await request('https://image.toast.com/aaaaab/ticketlink/TKL_3/ion_main08061242.jpg').on('error', function (e) {
  console.error(e);
}).pipe(transformer).on('error', function (e) {
  console.error(e);
}).pipe(writeStream).on('error', function (e) {
  console.error(e);
});
cs


하지만 이렇게만 하면 에러는 잡을 수 있어도 pipe에서 행이 걸리는 경우가 발생된다. 그래서 에러가 발생했을 때 행 걸리지 않고 다음 로직으로 정상적으로 처리되도록 하기 위해서는 this.emit('end')를 넣어줘야 한다.


1
2
3
4
5
6
7
8
9
10
11
// 파일로 쓰기
await request('https://image.toast.com/aaaaab/ticketlink/TKL_3/ion_main08061242.jpg').on('error', function (e) {
  console.error(e);
  this.emit('end');
}).pipe(transformer).on('error', function (e) {
  console.error(e);
  this.emit('end');
}).pipe(writeStream).on('error', function (e) {
  console.error(e);
  this.emit('end');
});
cs


참고

https://stackoverflow.com/questions/21771220/error-handling-with-node-js-streams


데이터베이스/Elasticsearch

Elasticsearch query string 조회시 parse exception 에러 처리

elasticsearch에서 query_string로 데이터 조회시에 쿼리문으로 ) 특수문자가 포함하여 조회했다. 하지만 다음과 같이 문제가 발생했다.


1
2
3
4
5
6
7
8
9
10
11
{
  "error": {
    "root_cause": [
      {
        "type": "parse_exception",
        "reason": "parse_exception: Encountered \" \")\" \") \"\" at line 1, column 11.\nWas expecting one of:\n    <EOF> \n    <AND> ...\n    <OR> ...\n    <NOT> ...\n    \"+\" ...\n    \"-\" ...\n    <BAREOPER> ...\n    \"(\" ...\n    \"*\" ...\n    \"^\" ...\n    <QUOTED> ...\n    <TERM> ...\n    <FUZZY_SLOP> ...\n    <PREFIXTERM> ...\n    <WILDTERM> ...\n    <REGEXPTERM> ...\n    \"[\" ...\n    \"{\" ...\n    <NUMBER> ...\n    "
      }
    ],
    "type": "search_phase_execution_exception",
    "reason": "all shards failed",
    "phase": "query",
cs


확인해보니 + - = && || > < ! ( ) { } [ ] ^ " ~ * ? : \ / 포함된 문장을 query_string 통해서 조회하려고 하면 에러를 발생시킨다. 그래서 이를 해결하기 위해서 위에 reserved character들이 들어간 단어는 \\를 붙여주어야 한다.


이를 위한 자바스크립트는 다음과 같다.

1
2
3
async escapeReservedCharacter(query) {
  return query.replace(/([!*+&|()<>[\]{}^~?:\-="/\\])/g, '\\$1');
}
cs


이를 해결해서 query_string을 사용하면 문제가 해결된다.


참고 : https://stackoverflow.com/questions/26431958/escaping-lucene-characters-using-javascript-regex




web/node.js

maxmind의 geoLite2를 이용해서 접속한 사용자의 지역정보 가져오기

서비스를 운영하다보면 사용자 아이피에 따라 장소에 맞는 상품을 추천해줘야 할 때가 있다.

그럴때 사용하는게 geoIp인데 이런 서비스를 제공하는 회사는 대표적으로 maxmind, db-ip, ipstack, ip2location 등이 존재한다.


그 중에 무료로 사용하기에 geoLite2가 좋다. geoLite2는 62%가 일치하고 geoIp2는 66% 일치한 정보를 제공한다. 그래서 무료로 사용할 수 있는 geoLite2를 사용해보자.


데이터베이스 다운로드

먼저 지역정보를 보관하고 있는 데이터베이스를 다운받아야 한다. 데이터베이스는 csv와 mmDB를 제공한다.  

링크 : https://dev.maxmind.com/geoip/geoip2/geolite2/ 


mmDB에서 데이터를 확인하기 어렵기 때문에 csv 파일에서 제공하는 정보를 보자. 우리가 필요한 정보는 사용자가 접속한 city (도시)정보가 필요한데 그 필드가 geoname_id로 되어 있다. 그 geoname_id는 지역에 고유 아이디로 이 아이디에 맞는 city 정보를 찾는 방법은 아래 주소를 통해 xml 형태로 받아 볼 수 있다. username 값은 회원가입을 통해 등록 할 수있다.

1
http://ws.geonames.org/get?geonameId=${geoname_id}&style=full&username=${userName}
cs


그럼 node.js에서 이 데이터베이스와 사용자 ip를 사용하여 city 정보를 추출하는 방법을 확인해보자.


우선 아래 주소에서 maxmind npm을 다운로드 받는다.

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


그리고 maxmind 라이브러리를 사용해서 데이터베이스를 로드하고 ip를 사용하여 지오로케이션 정보를 조회 할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
'use strict';
 
const maxmind = require('maxmind');
const path = require('path');
 
class MaxMindService {
 
  getIp(userIp) {
    const cityLookUp = maxmind.openSync(path.join(__dirname, './db/GeoLite2-City.mmdb'));
    const city = cityLookUp.get(userIp);
 
    return city;
  }
 
};
 
 
module.exports = new MaxMindService();
cs


테스트 코드를 만들어서 결과를 확인해보자.

1
2
3
4
5
6
7
8
9
10
11
'use strict';
 
const geoIp = require('../../lib/maxMind');
 
 
describe('geo IP', function () {
  it('아이피 정보로 지역정보 찾아보기', () => {
    console.log(geoIp.getIp('121.131.27x.1xx'));
  });
 
});
cs


결과를 보면 ip에 맞는 위치정보가 출력된다.

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
{ city: { geoname_id: 1841277, names: { en: 'Mapo-gu' } },
  continent: 
   { code: 'AS',
     geoname_id: 6255147,
     names: 
      { de: 'Asien',
        en: 'Asia',
        es: 'Asia',
        fr: 'Asie',
        ja: 'アジア',
        'pt-BR''Ásia',
        ru: 'Азия',
        'zh-CN''亚洲' } },
  country: 
   { geoname_id: 1835841,
     iso_code: 'KR',
     names: 
      { de: 'Südkorea',
        en: 'Republic of Korea',
        es: 'Corea del Sur',
        fr: 'Corée du Sud',
        ja: '大韓民国',
        'pt-BR''Coreia do Sul',
        ru: 'Южная Корея',
        'zh-CN''大韩民国' } },
  location: 
   { accuracy_radius: 10,
     latitude: 37.5544,
     longitude: 126.9093,
     time_zone: 'Asia/Seoul' },
  registered_country: 
   { geoname_id: 1835841,
     iso_code: 'KR',
     names: 
      { de: 'Südkorea',
        en: 'Republic of Korea',
        es: 'Corea del Sur',
        fr: 'Corée du Sud',
        ja: '大韓民国',
        'pt-BR''Coreia do Sul',
        ru: 'Южная Корея',
        'zh-CN''大韩民国' } },
  subdivisions: [ { geoname_id: 1835847, iso_code: '11', names: [Object] } ] }
Process finished with exit code 0
 
cs


 [ 1 ]  [ 2 ]  [ 3 ]  [ 4 ] 

푸터바

알림

이 블로그는 구글에서 제공한 크롬에 최적화 되어있고, 네이버에서 제공한 나눔글꼴이 적용되어 있습니다.

카운터

  • Today : 0
  • Yesterday : 460
  • Total : 82,691