「Webフロント開発編(1)」に続き、本編では登録画面からのデーターを処理する仮登録(confirm)機能の開発を行います。この開発では、ビジネスロジック、DBのエンティティ周りを別のプログラムファイル(service.js、model.js)に記述します。action.js内にすべての処理をまとめると可読性、保守性、再利用性が悪くなったりしますので。そのため「Webフロント開発編(1)」に比べ、作成するプログラムファイル、ソースコード数も多くなります。
テーブルスキーマ作成
登録ページから渡ってきたリクエストデータを保存するための、テーブルスキーマを設計します。Webアプリに登録するデーターを管理するということで、テーブル名はmemberにします
CREATE TABLE `member` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`email` char(40) NOT NULL
COMMENT 'メールアドレス',
`status` enum('waiting','active','inactive') NOT NULL
COMMENT '会員ステータス',
`keyword` text NOT NULL
COMMENT '勉強会検索キーワード',
`uid` char(40) NOT NULL
COMMENT 'email + keywordでユニーク性を持たせるためのID',
`timestamp` timestamp NOT NULL DEFAULT
CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=1 DEFAULT CHARSET=utf8
※statusカラムの状態の説明
- waiting・・・仮登録状態のユーザ
- active・・・登録が正常完了している有効会員
- inactive・・・本サービスを退会、または無効となった会員
ビジネスロジックを担当するservice.jsを作成
POSTで渡ってくるリクエストデータの中から、email(Eメールアドレス)、keyword(勉強会を探すためのキーワード)をオブジェクトのプロパティにセットするため、下記のように定義します。trim()メソッドは入力時に全角半角スペースがkeywordの前後に含まれていた場合のトリミングするためのメソッドです。commpaReplace()メソッドは入力されたkeywordが複数あった場合に、AND検索にするため、Google Calendar APIに値を投げる前にカンマ区切りにデータを変更するためのメソッドです。
[service.js]
/**
* action.js、batch.jsのビジネスロジックの
* 定義
*/
var config = require('./config.js');
exports.Service = {
email: "",
keyword: "",
/**
* トリミング(全角、半角のスペース削除)
*/
trim : function(key) {
return key.replace(/^\s+|\s+$/g, '');
},
/**
* カンマ(,)でreplace
*/
commaReplace: function(key) {
return key.replace(/\s+/g, ',');
}
};
DB Entityを担当するmodel.jsを作成
model.jsではmemberテーブルを操作するDB関係のメソッドを定義していきます。コーディング前に次の依存ライブラリが必要になりますのでダウンロードしてセットアップしてください
MySQLに接続するためのJava用JDBCドライバー
Javaのjarファイルは$RINGO_HOME/libに置くことでRingoJS内で呼び出すことができます。新しくjarファイルを追加する場合は忘れずにRingoJSの再起動を行っておきます
http://www-jp.mysql.com/downloads/connector/j/ より mysql-connector-java-5.1.16.tar.gzをダウンロードして解凍する $ cp mysql-connector-java-5.1.16/mysql-connector-java-5.1.16-bin.jar $RINGO_HOME/lib
RingoJS用O/Rマッパライブラリ
RingoJSのパッケージインストラーを使い、ringo-sqlstore、ringo-storableをインストールします。ringo-storableはringo-sqlstoreの依存パッケージです。インストールが完了しますと$RINGO_HOME/packages/に置かれる
$ $RINGO_HOME/bin/ringo-admin install oberhamsi/ringo-sqlstore $ $RINGO_HOME/bin/ringo-admin install hns/ringo-storable
MAPPING_MEMBER.propertiesでテーブルカラム情報を定義しています。Member.loadObjectプロパティを実行することでO/Rマッピングが行われます
[model.js]
/*
* Databaseを操作する
*/
var config = require('./config.js');
var Store = require("ringo/storage/sql/store").Store;
/**
* MySQLの接続情報
*/
var store = new Store({
"url": config.db.url,
"driver": config.db.driver,
"username": config.db.username,
"password": config.db.password
});
/**
* Member Entity defined
*/
var MAPPING_MEMBER = {
"table": "member",
"properties": {
"email": {"type": "string", "column": "email", "nullable": false},
"status": {"type": "string", "column": "status", "nullable": false},
"keyword": {"type": "string", "column": "keyword","nullable": false},
"uid": {"type": "string", "column": "uid", "nullable": false}
}
};
/**
* Memberオブジェクトの生成、各種メソッド定義
*/
exports.Member = {
loadObject: store.defineEntity("Member", MAPPING_MEMBER),
};
config.jsにMySQLの接続情報を追加します
[config.js]
exports.db = {
url: 'jdbc:mysql://localhost/test',
driver: 'com.mysql.jdbc.Driver',
username: 'hogehoge',
password: 'fugafuga'
};
登録確認処理の作成(http://localhost:8080/confirmの処理)
action.jsにPOSTで値を受け取れる関数を作成するために、index処理の下にconfirmという名前でスケルトンを作成します。リクエストをGETで受けるときは、exports.confirm.GETと変更します
[action.js]
/**
* confirm page (※POST Only)
*/
exports.confirm = {};
exports.confirm.POST = function (req) {
//処理を記述する
}
exports.confirm = {};でオブジェクトを生成しています。この初期化がないと、以下のようなエラーメッセージがでて怒られます。
TypeError: Cannot set property "POST" of undefined to "org.mozilla.javascript.gen.actions_js_97@252a17" (actions.js#19)
model.js、service.jsのrequire
作成した、model.jsとservice.jsをコールすために、以下のrequire文をaction.jsの先頭に追加します。{}でくくることによって、model.js、service.jsの中にある指定したオブジェクトだけを呼び出すことができます。今回のmodel.js、service.jsともに1ファイルに1オブジェクトしかないので、あまりメリットが感じませんが、1ファイルに複数のオブジェクトが存在する場合は、使用しないオブジェクトはCallされないので、メモリ消費を抑えるメリットがでてくるんだと思います。
[action.js]
var {Member} = require('./model.js');
var {Service} = require('./service.js');
リクエストパラメータのチェック
action.jsに次のコードを追加します。Serviceオブジェクトのプロパティ(email, keyword)にリクエストパラメーターをセットします。その下で値が空かどうかのチェックを行っています。どちらかが空であった場合は、skins/index.htmlを呼び出し、テンプレートファイルのerrorMessage変数が赤色で出力されます。
本格的にサービスを運営する場合は、このチェックだけでは不十分ですので、Validationチェックを行う必要があると思います。ここでは時間の都合で最低限の値チェックしか行わないでおきます。
[action.js]
Service.email = req.params['email'];
Service.keyword = req.params['keyword'];
if (Service.email === "" || Service.keyword === "") {
return Response.skin(module.resolve('skins/index.html'), {
title: 'エラー',
errorMessage: '入力した値に問題があります、再度入力してください'
});
}
email、keywordを空にして、subimitを行い、エラー画面を表示してみます。赤丸で囲ったtitleの個所が「エラー」という文字に切り替わっており、errorMessageも赤色で「入力した値に問題があります、再度入力してください」表示されているのが確認できました

多重登録防止
多重登録が可能になってしまいますと、悪意ある第3者に登録確認のメールを利用したSpam装置に利用される危険性もあるので、ここでは多重登録防止機能を作成します。仕様的には、memberテーブルのstatus=waitingが同じメールアドレスで存在した場合は、エラーにしたいと思います。
model.jsに次のメソッドを追加します。equals('email',service.email).select()でservice.emailと同じemailアドレスがないかどうか探しにいっています。result変数は配列で中身がかえってきますので、配列が1件以上存在していて、中にwaiting状態であれば、falseとしてエラーにしています
[model.js]
/**
* メールアドレスをキーにして多重登録の有無を確認する
*/
check: function() {
var result =
this.loadObject.query().
equals('email',service.email).select();
if (result.length !== 0) {
for each(res in result) {
if (res.status === 'waiting') return false;
}
}
return true;
},
action.jsにMember.check()メソッドを追加してエラー処理を記述します。
[action.js]
if (Member.check() === false) {
return Response.skin(module.resolve('skins/index.html'), {
title: 'エラー',
errorMessage: '既に申し込み済みのメールが存在しております、' +
'お手元のメールを確認の上そちらを先に登録完了ください'
});
}
DBへの仮登録処理
model.jsに次のconfirmメソッドを追加します。new this.loadObject()でオブジェクトを呼び出して、テーブルにinsertする値を代入していきます。keywordの代入では、Service.trim()メソッドで余計な空白を取り除き、複数のキーワードに対しては、カンマ区切りをService.commaReplace()メソッドで行っています。最後にinsertを行う、sqlstoreオブジェクトのsave()メソッドを実行しています。sqlstore自身Transactionも実行可能なのですが、今回は簡略化のため実装を省いています。
[model.js]
/**
* 仮登録処理
*/
confirm: function(uid) {
var member = new this.loadObject();
member.email = Service.email;
member.status = 'waiting';
member.keyword = Service.commaReplace(Service.trim(Service.keyword));
member.uid = uid;
member.save();
},
service.jsにユニークIDを生成するメソッドを追加します。時間をキーにハッシュ化してユニークIDにしたいと思います。ハッシュ化関数として、RingoJSのコアモジュールにデフォルトでバンドルされているstringsモジュールを使用します。このモジュールは名前からしてわかりますように、文字列を操作するライブラリ群になっており、digest()メソッドも存在しています。内部でjava.security.MessageDigestクラスをラップしており、MessageDigestクラスで対応しているハッシュアルゴリズム(MD5、SHA-256など)が使用可能です。MD5は解読される可能性が高いのでセキュリティ的にはSHA-256などを使用すべきだと思いますが、簡略させるためMD5を使用しています。特にハッシュアルゴリズムを指定しない場合、自動的にMD5になります。
[service.js]
var strings = require('ringo/utils/strings');
/**
* ユニークIDの作成
*/
createUniqId: function() {
return strings.digest(new Date().toString());
},
action.jsに作成したMember.confirm()メソッドを実装したいと思います。uidを生成して、Member.confirm()メソッドに渡します
[action.js]
var uid = Service.createUniqId(); Member.confirm(uid);
メール送信処理
RingoJSのパッケージインストラーを使い、ringo-mailをインストールします。内部でjavax.mailをたたいているパッケージです
$ $RINGO_HOME/bin/ringo-admin install emilis/ringo-mail
service.jsにsendMail()メソッドを追加します。値が変わる箇所としては宛先(To)とメール本文(Body)なので、それは外部から値を受けれるようにしておきます。それ以外の宛先(From)と件名(Subject)は、今回は固定値にするため、config.jsに追加したいと思います
[service.js]
var email = require('ringo/mail');
/**
* 確認メールを入力されたメールアドレスに送信する
*/
sendMail: function(to, body) {
email.send({from: config.email.from, to: to,
subject: config.email.subject, text: body});
},
メールテンプレート作成
メールテンプレートもHTMLテンプレートの作成時に使用した、Response.skin()メソッドを使って実装しています。completeUrlはメール本文に出力される、登録完了ページ(次編で開発を行います)のURLになります。ユーザーを特定するためにURLのクエリストリングにuidを付属しDBにあるmemberテーブルのuidカラムとのマッチングを行うために使用します
[action.js]
var {Response} = require('ringo/webapp/response');
/**
* メールフォーマットの作成
*/
exports.Service.textFormat = {
confirm: function(uid) {
var sv = exports.Service;
return Response.skin(module.resolve
('skins/email/confirm.tpl'),
{ keyword: sv.commaReplace(sv.trim(sv.keyword)),
completeUrl : config.fqdn
+ "/complete?uid=" + uid}).body;
},
};
Response.skin()メソッドはhtmlテンプレートだけでなく、どのようなタイプのテンプレートでも対応できるようになっているので、オリジナルのメール専用のテンプレートを作成して変数をセットしてあげるだけでOKです
(1)HTMLテンプレートをセットする場合 return Response.skin(module.resolve ('skins/index.html') ↓ (2)メールテンプレートをセットする場合 return Response.skin(module.resolve ('skins/email/confirm.tpl')
skins/email/confirm.tplを作成します
[skins/email/confirm.tpl]
<IT 勉強会サーチメール>にお申し込み有り難うございます。 ご入力頂きましたメールアドレスに対して登録確認のための メールを自動送信しています。 ---------------------------------- 検索キーワード:<% keyword %> ---------------------------------- ご確認がとれましたら、下記URLにアクセスして登録を 完了してください。 (※本メールをお受け取りになりましてから、3日以内に 登録を完了しませんと無効となります) <% completeUrl %> 尚、本メールにつきまして心あたりがない方は お手数ですが、破棄していただけますでしょうか。
config.jsには次の値を追加します
[config.js]
exports.fqdn = 'http://localhost:8080';
exports.email = {
from: 'it_study_search@fukaoi.org',
subject: 'IT勉強会サーチメール'
};
actions.jsにメール送信する処理を実装
action.jsにメール送信のためにメールテンプレートから本文を作成して、それをService.sendMail()メソッドの第2引数として渡しています。そして仮登録のメッセージをResponse.skin()メソッドで設定しています。
[action.js]
Service.sendMail(Service.email, Service.textFormat.confirm(uid));
return Response.skin(module.resolve('skins/index.html'), {
title: '受付致しました',
headMessage: '入力いただいたメールアドレスに登録完了のURLを送信しました'
});
実際に送信されたメールの内容です。検索キーワードとして「JavaScript 東京」と入力しました

仮登録完了したページは以下のように、テンプレートは共通のskins/index.htmlですが、赤丸で囲んだ個所で、title、headMessageの文言が上記設定したaction.jsのResponse.skin()メソッド内容に置き換わっていることが確認できます

以上が仮登録機能の開発になります。次編から登録完了ページの開発に入ります。
Webフロント開発編(3)へ
Trackback URL
http://blog.fukaoi.org/2011/05/30/server-side-javascript-webdev2?tb=y&entry_id=36
XML
