QRコード
QRCODE
アクセスカウンタ
読者登録
メールアドレスを入力して登録する事で、このブログの新着エントリーをメールでお届けいたします。解除は→こちら
現在の読者数 0人
オーナーへメッセージ
slMame › 一日一膳腹八分目 › 2009年08月13日

  

Posted by at

2009年08月13日

情報管理システムプロトタイプ

http://xiao.slmame.com/e705867.html
で会話していた内容で、実際にサーバーに情報を溜め込むためのサーバー側が、とりあえず動き出しました。
MySQL4+PHP5にて。
稼動しているURLはブログにはかけませんが、どういう代物なのかを簡単に書いておくほうが会話がスムーズになる気がしますので、仕様といいますか。。。

使用しているPHPプログラムは私のオリジナルですので、探しても手に入りません。
名前はUnauna-Umiといいます。



モジュール(初期設定ファイルおよび追加関数)の中身


//Unauna-Umi-Module
/*************************************************************************
Unauna-SECONDLIFE
セカンドライフ情報管理/サーバー側モジュール
Copyright(c)RBK 2009.
**************************************************************************/
write("userdatas") {
id=3;
userid="demo";
passwd="demo";
permission="normal";
visible=1;
default_table='customer_data';
default_shopping_table='customer_shopping_data';
}

とりあえずデモアカウントをひとつ発行しています。
顧客情報テーブルと、購入履歴情報テーブルをユーザー毎に指定できるようにしてあります。
permissionでいろいろ切り分けできるようにしておきました。





/*************************************************************************
テーブル
**************************************************************************/
createTable("customer_data"){
}
createColumn("customer_data"){
userid,text,default '';
passwd,text,default '';
visible,int,default 1;
uuid,text,default '';
point,numeric,default 0;
rez_date,text,default '';
rez_date_unix,int,default 0;
}
createIndex("customer_data"){
uuid="customer_data_uuid";
}

顧客情報テーブルです。
オーナーのIDとパスワード、顧客のUUID、ポイント残高、誕生日とそのUNIXタイムを作らせて見ました。
ユーザーUUIDをキーにする検索が多そうなのでインデックスを張っています。





createTable("customer_shopping_data"){
like="customer_data";
}
createColumn("customer_shopping_data"){
item_name,text,default '';
item_uuid,text,default '';
price,int;
}
createIndex("customer_shopping_data"){
item_name="customer_shopping_data_item_name";
item_uuid="customer_shopping_data_item_uuid";
}

お買い物履歴のテーブルです。
顧客の情報に加え、商品名とそのUUID、価格のカラムを追加してみています。





/*************************************************************************
関数
**************************************************************************/
function mkXML(datas){
var res='';
foreach(datas as $did=>$data){
switch (did){
case "tablename":
case "parenttable":
case "contentstype":
case "templatefile":
case "usermode":
case "userid":
case "passwd":
break;
default:
res &&&='<'&&&did&&&'>'&&&data&&&'</'&&&did&&&'>';
}
}
return res;
}

XMLテンプレートで使う関数mkXML()を記述しています。
&&&は文字列の連結です。
内部処理用のカラム以外のものを、カラムの名前をタグに変換して囲んで返します。
useridとpasswdカラムも見せると嫌な気配なので生成しないことにしています。

カラム名称をそのまま使うのが気持ち悪ければ少し改造ですね。






function checkOwnerUUID(){
flags.customerDataTable="";
flags.customerShoppingDataTable="";
if (!isset(submitted.owner_userid) || !isset(submitted.owner_passwd) || !submitted.owner_userid || !submitted.owner_passwd) {
flags.errorMessage='認証情報が不正です。引数が足りません
';
return false;
}
var test=readData('userdatas','*',"userid='"&&&submitted.owner_userid&&&"' AND passwd='"&&&submitted.owner_passwd&&&"'");
if (!test.count) {
flags.errorMessage='認証情報が不正です。認証ができませんでした
';
return false;
}
flags.customerDataTable=test.0.default_table;
flags.customerShoppingDataTable=test.0.default_shopping_table;
return true;
}

オーナーのユーザーIDとパスワードで使うテーブル名を変数flagsの所定のキー名に入れている関数です。
エラーがある場合変数flags.errorMessageにエラーが入ります。

XMLテンプレートで場合わけに使うために、boolの戻り値を返します。
後々複数ユーザーによって利用することを前提にする為ですね。





XMLテンプレート


<?xml version="1.0" encoding="UTF-8"?>
<response>
<doit if="submitted.rezDate">
<settarget sorce="rezDate" targetcolumn="rez_date" checktype="match:/^[0-9]{4}-[0-9]{2}-[0-9]{2}$/" print="誕生日" />
<settarget exec="var tmp=explode('-',submitted.rezDate);submitted.rezUnixTime=mktime(0,0,0,tmp[1],tmp[2],tmp[0]);" />
<settarget sorce="rezUnixTime" targetcolumn="rez_date_unix" checktype="int" print="誕生日(UNIX)" />
</doit>
<doit if="submitted.submitted">
<doit if="checkOwnerUUID()">
<settarget sorce="owner_userid" targetcolumn="userid" checktype="required" print="Ownerの認証ID" />
<settarget sorce="owner_passwd" targetcolumn="passwd" checktype="required" print="OwnerのPassword" />
<settarget sorce="uuid" targetcolumn="uuid" checktype="required" print="UUID" />
<settarget sorce="point" targetcolumn="point" checktype="numericif" print="Point" />
<doit if="submitted.submitted=='pay'">
<settarget sorce="itemName" targetcolumn="item_name" checktype="required" print="商品名称" />
<settarget sorce="itemUUID" targetcolumn="item_uuid" checktype="required" print="商品UUID" />
</doit>
<settarget exec="checkSubmitted();" />
<settarget if="!flags.errorMessage && submitted.submitted=='pay'" exec="flags.tmpTargetId=submitted.targetId;submitted.targetId='';writeSubmitted(flags.customerShoppingDataTable);submitted.targetId=flags.tmpTargetId;" />
<settarget if="!flags.errorMessage" exec="writeSubmitted(flags.customerDataTable);" />
</doit>
</doit>
<settarget only="category" sort="updated:desc" />
<doit if="checkOwnerUUID()">
<settarget if="submitted.type=='customer'" use="flags.customerDataTable" />
<settarget if="submitted.type=='shopping'" use="flags.customerShoppingDataTable" />
<settarget sorce="owner_userid" target="userid" type="=" />
<settarget sorce="owner_passwd" target="passwd" type="=" />
<settarget sorce="uuid" target="uuid" type="=" />
<settarget sorce="rezDate" target="rez_date" type="=" />
<settarget sorce="rezUnixTime" target="rez_date_unix" type="=" />
</doit>
<doit if="flags.errorMessage"><error>1</error></doit><doit if="!flags.errorMessage"><error>0</error></doit>
<message>{errorMessage}</message>
<count>{count}</count>
<body>
<doit if="db.count"><doit exec="flags.dataXML=mkXML(db)" output="datas" limit="none">{dataXML}</doit></doit>
</body>
</response>

作動内容

このコアは検索条件の類をDreamweaverなどのHTMLエディタ上でざくざく書いていくことが出来、実質の動作順序は(大半は)テンプレートで指示します。
ブログが流行し始める前から仕様を勝手に決めて作っていたもので、タグの入力補完機能を使って楽をしたかったので、命令が全部タグになっています。

<?xml version="1.0" encoding="UTF-8"?>
<response>


とりあえずルートタグで囲まないとパーサー通らないので、MTのトラックバックを真似てみました。
何でもいいです。


<settarget exec="flags.dataEditTarget='targetId'" />

書き込み要求を送信する際に、データーベース側の管理番号が入っているクエリの名前を指定しています。
このクエリ名の内容を基にしてデーターを追加するか、アップデートするかが決まります。


<doit if="submitted.rezDate">

クエリ引数にrezDateがあれば、中身を評価します。

<settarget sorce="rezDate" targetcolumn="rez_date" checktype="match:/^[0-9]{4}-[0-9]{2}-[0-9]{2}$/" print="誕生日" />

正規表現で****-**-**の日付形式になっているかチェックするように指示し、書き込みする場合はカラムrez_dateに書き込むように指示しています。

<settarget exec="var tmp=explode('-',submitted.rezDate);submitted.rezUnixTime=mktime(0,0,0,tmp[1],tmp[2],tmp[0]);" />

誕生日日付をUNIXタイムスタンプに変換させています。


<settarget sorce="rezUnixTime" targetcolumn="rez_date_unix" checktype="int" print="誕生日(UNIX)" />

誕生日UNIXタイムスタンプの書き込み先をrez_date_unixに指定しています。
今のところ意味はありませんが、整数であるかもチェックするように指示しています。

</doit>




<doit if="submitted.submitted">

クエリ引数にsubmittedというものがあり、値が何かはいっていて0でなければ、中身を評価します。
とりあえず、これに何か値がある場合、動作としては書き込みさせることにしました。


<doit if="checkOwnerUUID()">

追加した関数checkOwnerUUID()を呼び出して書き込み先のテーブルをセットしようとしています。
戻りの値がfalseの場合はこの中は実行されません。


<settarget sorce="owner_userid" targetcolumn="userid" checktype="required" print="Ownerの認証ID" />
<settarget sorce="owner_passwd" targetcolumn="passwd" checktype="required" print="OwnerのPassword" />
<settarget sorce="uuid" targetcolumn="uuid" checktype="required" print="UUID" />
<settarget sorce="point" targetcolumn="point" checktype="numericif" print="Point" />


書き込み先とクエリの対応関係を指定し、情報チェックの方法も指定しています。
ポイントは「送信されてきたなら実数」であること、その他は必須入力の指定になっています。


<doit if="submitted.submitted=='pay'">
<settarget sorce="itemName" targetcolumn="item_name" checktype="required" print="商品名称" />
<settarget sorce="itemUUID" targetcolumn="item_uuid" checktype="required" print="商品UUID" />
</doit>


クエリ名submittedの値がpayだった場合は、購入したアイテムの名前とUUIDも書き込むよう、書き込み先の指定と必須入力の指定をしています。


<settarget exec="checkSubmitted();" />

実際に入力情報をチェックする関数checkSubmitted()を呼び出しています。
エラーがあった場合はflags.errorMessageにエラー記述が入ります。



<settarget if="!flags.errorMessage && submitted.submitted=='pay'" exec="flags.tmpTargetId=submitted.targetId;submitted.targetId='';writeSubmitted(flags.customerShoppingDataTable);submitted.targetId=flags.tmpTargetId;" />

購入情報を、エラーがなければかきに行かせています。
submitted.targetIdの値をいじっているのは、指定されている場合更新だとみなそうとするからです。
買い物履歴は常に追加ですから、submitted.targetIdの値をクリアしています。


<settarget if="!flags.errorMessage" exec="writeSubmitted(flags.customerDataTable);" />

エラーがないなら、checkOwnerUUID()で拾わせた顧客情報テーブルに書き込みに行かせています。
この際、クエリ名targetIdの値がセットされていて、当該管理番号の情報があれば、アップデート、targetIdの値がなければ新規追加になります。
とりあえず更新させておくとカラムupdatedの値がその時刻のものになるので、最終利用時刻を拾えます。



</doit>
</doit>



<settarget only="contents" sort="updated:desc" />

テーブル情報ではなく、実際のデーターだけを扱うようにonly属性で指示し、さらに並び順を更新時刻の逆順にしています。



<doit if="checkOwnerUUID()">
とりあえず後から見ても思い出せるように、二度手間ですがオーナーのIDとパスワードでテストします。
戻りの値をあらかじめ何かに保存しておけば不要な関数呼び出しですね。

<settarget if="submitted.type=='customer' || !submitted.type" use="flags.customerDataTable" />
<settarget if="submitted.type=='shopping'" use="flags.customerShoppingDataTable" />


引数typeの値で、検索対象にするテーブルの名称を変えています。


<settarget sorce="owner_userid" target="userid" type="=" />
<settarget sorce="owner_passwd" target="passwd" type="=" />
<settarget sorce="uuid" target="uuid" type="=" />
<settarget sorce="rezDate" target="rez_date" type="=" />
<settarget sorce="rezUnixTime" target="rez_date_unix" type="=" />


各々のsorce属性にある名前の送信情報で、検索を行うべきカラムの名称と、一致のタイプを指定しています。
全部、完全一致検索になっています。
このほかにmatch属性を指定して結合条件をORにすることも出来ますが、指定のない場合AND(絞込み)で連結されます。送信されてきていない設定内容は無視されます。
内部的には上の設定はこうなります。

WHERE (((userid='demo')) AND ((passwd='demo')) AND ・・・・・・・ AND ((rez_date_unix=11245341)))

</doit>




<doit if="flags.errorMessage"><error>1</error></doit><doit if="!flags.errorMessage"><error>0</error></doit>

エラーメッセージがあれば<error>1</error>、なければ<error>0</error>を採用します。


<message>{errorMessage}</message>

エラーメッセージをはかせています。


<count>{count}</count>

検索結果の数を書いています。


<body>
<doit if="db.count"><doit exec="flags.dataXML=mkXML(db)" output="datas" limit="none">{dataXML}</doit></doit>
</body>

検索結果があれば、関数mkXML()で作ったタグたちをあるだけループ処理で<body>の中に書かせています。


</response>

ルートタグを閉じて、おしまいです。

作動のフロー


プリムでおきた何らかのイベント時に顧客のUUID(イベントをしたアバターのUUID)を拾って、サーバー側に

http://example.com?type=customer&owner_userid=demo&owner_passwd=demo&uuid=***&limit=1

などのクエリで検索結果を要求します。
オーナーのuseridとパスワードを指定しないで検索結果を返すと個人情報漏れになってしまうため、プリムからの要求では、何をするにせよ、かならず引数に指定しておかなければなりません。また、機能拡張の際にも、そのようにXMLテンプレートを作ってやる必要があります。
戻りは

<?xml version="1.0" encoding="UTF-8"?>
<response>
<error>0</error>
<message></message>
<count>1</count>
<body>
<datas>
<id>3</id>
<date>1250134497</date>
<updated>1250139960</updated>
<visible>1</visible>
<uuid>****-**-******</uuid>
<is_receive>1</is_receive>
<rez_date>1970-01-01</rez_date>
<rez_date_unix>0</rez_date_unix>
<point>0</point>
</datas>
</body>
</response>
のようなXML文書になるので、プリム側ではこれらの情報を元に買い物など処理を行います。

最後に、処理結果に加えて上の戻りの<id>タグの中の数字を引数targetIdとして指定し、引数submittedにpay、ないし、なにか文字を入れて、書き込み要求として送信します。

http://example.com?submitted=pay&owner_userid=demo&owner_passwd=demo&targetId=3&uuid=***&・・・・・・・・・&limit=1


みたいな感じ。
書き込みが正常に成功すれば、書き込まれたデーターが同じXML形式で戻ってくるはずです。
失敗していると情報が見つからないため、<count>タグの中が0になります。または、入力情報チェックに引っかかった項目のエラーが<message>タグの中に羅列されてきます。


まとめ


要するに、

1:設定モジュールのテーブルのカラムを作っているところに増減してフラグや情報の数はすぐ増やせる。
2:<settarget>タグのsorce属性指定とtargetclumn属性指定で書き込むべきクエリ名とカラム名の対応関係は任意に変えられる。
3:<settarget>タグのsorce属性指定とtarget属性指定でクエリ名と検索対象とすべきカラム名の対応関係は任意に変えられ、複数条件指定にも自由に対応できる。
4:あちこちにあるif属性で場合わけできるので作動内容も任意に増やせる。


という動作をテンプレをいじるだけでしますので、拡張に関するサーバー側の問題はあまり考えなくて良いかと思います。
欠点は、


1:汎用コアなので基本的に動作がまったり。
2:複数ユーザーに使わせる場合、ユーザーに許す変更の範囲に気をつけないとセキュリティホールが出来てしまう。
3:このままでは正規化とかを一切考えていないのでデーターは冗長。メモリが少ないと止まるかも。
4:簡易CMS用のコアなので、副問い合わせなどを使う場合は関数にして戻りをとる方式で検索させないと出来ない。

といったところです。。
  


Posted by RBK Drachnyd(しお) at 16:42Comments(3)Unauna-Umi