[Mongoose] Schema의 Virtual, Method, Pre 테스트
Mongoose를 사용할 경우 몇가지 유용한 API를 제공하고 있다. 이중 virtual, method, pre에 대한 사용법을 알아보자. 예제는 로그인시 패스워드를 암호화 하는 코드를 가지고 파악해 본다
1. Schemas
- http://mongoosejs.com/docs/guide.html 의 Schemas에 대한 정의와 플러그인 확장이 가능하다
- 스키마 인스턴스를 만들었다면 여기에 methods, statics, indexes, virtuals, options등을 설정할 수 있다
- 플로그인들은 http://plugins.mongoose.com에서 검색가능하다. 현재 나는 _id에 대한 auto increase 기능을 잘 쓰고 있다
// 1) 스키마 만들기 : 도큐먼트 구조의 정의
var ComponentSchema = new mongoose.Schema({
_id : { type: Number },
name : { type: String },
type : { type: String },
config : { type: String },
updated_on : { type: Date, default: Date.now }
});
// 2) 플러그인 확장 & methods, statics, indexes, virtuals, options등 설정
ComponentSchema.plugin(autoinc.plugin, {
model : 'sd_component',
field : '_id',
start : 200000,
step : 1
});
// 3) 모델은 스키마 정의에 대한 컴파일된 생성자이다. Model 인스턴스를 통해서 몽고디비로부터 데이터를 CRUD한다
var Component = mongoose.model('sd_component', ComponentSchema);
- 샘플로 UserSchema 생성하기
var UserSchema = new mongoose.Schema({
username : {type:String, required:true , unique:true},
hashed_password : {type:String, required:true},
salt : {type:String, required:true}
});
2. Instance Methods
- 스키마가 만들어지고 난후 기본적으로 설정된 메소드를 상속받아 사용한다 예) findById
- 사용자 커스텀 메소드를 정의할 수 있다. 처리 결과를 return 한다.
- statics는 callback을 파라미터로 넘기고, return이 없다. (참조)
// 메소드안에서 메소드 호출이 가능하다
UserSchema.method('authenticate', function(plainText) {
console.log('authenticate called:')
console.log('plain text = ' + plainText)
console.log('hashed = ' + this.encryptPassword(plainText))
console.log('db password= ' + this.hashed_password)
return this.encryptPassword(plainText) === this.hashed_password;
});
UserSchema.method('makeSalt', function() {
return Math.round((new Date().valueOf() * Math.random())) + '';
});
// crypto 모듈 사용하여 암호화
UserSchema.method('encryptPassword', function(password) {
return crypto.createHmac('sha1', this.salt).update(password).digest('hex');
});
UserSchema.method('generateToken', function() {
return crypto.createHash('md5').update(this.username + Date().toString()).digest("hex");
});
3. Virtual
- MongoDB에 저장되지 않는 편리한 Attribute이다 (참조)
- set/get 펑션을 정의한다
UserSchema.virtual('password')
.set(function(password) {
this._password = password;
this.salt = this.makeSalt(); // 사용자정의 메소드 호출
this.hashed_password = this.encryptPassword(password); // 사용자정의 메소드 호출
})
.get(function() { return this._password; });
// 모델 생성
var User = mongoose.model('User', UserSchema);
User.password = '1234'; // set
console.log(User.password); // get
4. Pre
- 몽구스의 middleware기능이다
- init, validate, save, remove 메소드 수행시 처리되는 미들웨어 펑션이다
- 복잡한 유효성검사, 트리거 이벤트 처리등. 예로 사용자를 삭제하면 사용자 관련 블로그포스트도 삭제하기같은 경우 사용
또는 에러 핸들링
UserSchema.pre('save', function(next) {
this.token = this.generateToken();
if (!validatePresenceOf(this.password || this.hashed_password)) {
next(new Error('Invalid password'));
} else {
next();
}
});
function validatePresenceOf(value) {
return value && value.length;
}
// ex)
User.save(function(err) { console.log(err.message); } );
- post : 사후처리
5. 전체 소스 코드
- User Model을 모듈로 만든다. User.js
- 애플리케이션에서는 User.js를 Model 인스턴스를 리턴받아서 사용한다
- Express의 Middleware에서 사용자 로그인시에 User.authenticate을 호출하여 패스워드를 암호화 한다
var mongoose = require('mongoose'),
crypto = require('crypto');
module.exports = function () {
var UserSchema = new mongoose.Schema({
username : {type:String, required:true , unique:true},
hashed_password : {type:String, required:true},
salt : {type:String, required:true}
});
UserSchema.virtual('password')
.set(function(password) {
this._password = password;
this.salt = this.makeSalt();
this.hashed_password = this.encryptPassword(password);
})
.get(function() { return this._password; });
UserSchema.method('authenticate', function(plainText) {
console.log('authenticate called:')
console.log('plain text = ' + plainText)
console.log('hashed = ' + this.encryptPassword(plainText))
console.log('db password= ' + this.hashed_password)
return this.encryptPassword(plainText) === this.hashed_password;
});
UserSchema.method('makeSalt', function() {
return Math.round((new Date().valueOf() * Math.random())) + '';
});
UserSchema.method('encryptPassword', function(password) {
return crypto.createHmac('sha1', this.salt).update(password).digest('hex');
});
UserSchema.method('generateToken', function() {
return crypto.createHash('md5').update(this.username + Date().toString()).digest("hex");
});
UserSchema.pre('save', function(next) {
this.token = this.generateToken();
if (!validatePresenceOf(this.password || this.hashed_password)) {
next(new Error('Invalid password'));
} else {
next();
}
});
return mongoose.model('User', UserSchema);
}
function validatePresenceOf(value) {
return value && value.length;
}
<참조>