はじめに
私は、さまざまな都市に10以上の支社があり、かなり不安定で低速なチャネルで結ばれている大企業で管理者として働く機会がありました。 他の多くの場合と同様に、会社の情報交換の基礎は電子メールでした。 本社と同様に、各支店にはメールサーバーがあり、メールアカウントの管理はローカル管理者が行うことに注意してください。 すべてのメールサーバーは、FreeBSD + Postfix + SpamAssassin + amavisd-new + Courier-IMAPを実行しています。
主なタスクは、社内で使用するすべての企業ユーザーの電子メールアドレスの最新のディレクトリを維持することです。セカンダリは、メインメールサーバーで使用可能な電子メールアドレスのリストです。
チャネルは不安定で、多くの場合「フォール」し、速度は一般に予測不能です。 タスクがどのように解決されたかを気にする人は、猫に招待します。
既往歴
メールトポロジ。会社のメールサーバーの関係スキームをスターと呼ぶことができる場合。 外部からアクセス可能な1つの中継サーバーがあり、これを介して会社の支店間、および会社のユーザーと外の世界との間でメールが交換されます。 サーバーでは、すべての受信メッセージが、外部からのスパムでもあるウイルスについてスキャンされます。 サーバーにメールボックスはありません。これは純粋にSMTPリレーです。
残りのサーバーは物理的にブランチオフィスにあり、1つは本社にあります。 外部の世界にアクセスすることはできず、メインサーバーから着信メッセージを受信し、発信メッセージはそこから送信されます。 主な機能は、エンドユーザーのメールボックスのメンテナンスです。
すべてのサーバーはFreeBSDバージョン7.x-8.xを実行し、PostfixはSMTPとして使用され、Courier-IMAPはPOP3 / IMAPとして使用されます。
データはどこから来たのですか
ユーザーメールアカウントはMySQLデータベースに保存され、PHPで作成されたWebインターフェイスを介してローカル管理者によって管理されます。 ブランチでは、2005年の私の気取らない開発。 PHP 4、本社のAJAX機能を使用した美しいYiiアプリケーション。 また、フレームワークの研究の一環として私によって書かれました。
解決策
当初、ディレクトリは業界ごとのすべての電子メールのリストを含むHTMLページとしてサーバーに保存されていました。 時間が経つにつれて、この決定は重要性の降順で、次の理由で私を満足させることをやめました:
- スパマー。 人々はシンプルで怠け者で、社内では非常に多くの人が、誰か他の人のアドレスを提供するよう求められ、ページを保存してメールで送信しました。 メールアドレスのリストが驚くべき速度で複製されました
- 信頼性 リストは、cronスクリプトを使用して各サーバーで1日に1回生成され、次にcronを使用してメインサーバーがリストを収集し、非常にHTMLページを形成しました。 チャネルがなかった場合、情報は更新されず、翌日の夜に繰り返し通信が途絶えた場合に前のコピーを保存して、リスト内のブランチの場所にギャップが生じました。 もちろん、スクリプトは開発されてよりスマートになりましたが、どのようにすれば美しいとは言えませんでした。
- 便利。 このアイテムを最初に置いておくのは良いことですが、私は管理者であり、ユーザーが私をモスクにするまでユーザーの利便性を常に心配しているわけではありません。 私にとって、主なことは、すべてが機能し、最小限の介入が必要なことです。 ただし、ディレクトリが拡大している可能性があるため、国内および市の電話番号、位置、ユニット、オフィス番号のフィールドが追加されました。 これらのフィールドはオプションで入力されましたが、ディレクトリも電話になったのはおかげです。 そのようなリストで人を探すことはますます難しくなりました。 遅いチャンネルのブランチでは、その負荷はますます長くなり、一般に、何かが醸造されていました。
輸送の選択
MySQLの組み込みツールを使用したレプリケーションから、ネットワークポートでリッスンする独自のサービスの作成までのオプションに基づいて、長い間その方法を考えました。 答えは、この間ずっと、近くに立ってにやにや笑いました。 それはすべてメール関連ですか? さて、ここにトランスポートがあります。 ほんの数秒の誤差でリアルタイムを必要とせず、チャネルがあれば1分以内にすべてが機能します。 チャネルがない場合、データはキューに入れられて待機し、配信されます。 一般的に、送信のみが可能で、残りは懸念事項ではありません。
そのため、データは電子メールメッセージを介して交換されることになりました。
アルゴリズム
データ交換は一方向で、メールサーバーは、管理ユーザーがユーザーベースを編集するときに、ディレクトリにある中央サーバーに変更に関するデータを送信します。
実装
最初のステップは、コマンドの形式を決定することです。 コマンドはレターの本文で送信されます。 メッセージの内容はテキスト/プレーンであり、添付ファイルはありません。つまり、base64またはquoted-printableですべてをエンコードするMIMEマルチパートはありません。
レターの件名には、変更するデータを一意に識別するためのユニット(ブランチ)の一意のコードが含まれます。 また、ユニットコードを知らずに(やや洗練された)システムのアルゴリズムを試してみると、「フーリガン」に対する少しの保護でもあり、変更はできません。不明なコードの文字は単に無視されます。 文字の本文には、次の形式の任意の行数を含めることができます。
_cmd=insert:type=mbox:name= :eml=ivanov@company.ru:vis=1:gor=88-02-93:mer=38-32-93:sot=:of=214:post= :sdiv=
_cmd=update:type=mbox:eml=petrov@company.ru:name= :act=1:vis=1:post=:of=822:mer=27-71:gor=88-02-49:sot=:sdiv= .
_cmd=delete:type=mbox:eml=sidorov@company.ru
これらの行からわかるように、電子メールアドレスでレコードを一意に識別します。 通常、効率的にコマンドを蓄積する意味がないため、レターには1つのコマンド(行)があります。 ただし、コマンドのパッケージ全体に対して1つの文字を使用する機能を異なるスクリプトが使用する場合があります。
コマンドはperlで記述されたスクリプトになります。 コマンドを含む文字をスクリプト入力ストリームに転送するには、内部ネットワークからのみアクセス可能なメールボックスが使用されます。 私のシステム(FreeBSDの接尾辞)に実装するために、/ etc / mail / aliasesにこのメールエイリアスを作成しました。
userdir: "|/home/admin/directory/userdir.pl"
ここでは、すでに大きな記事を過負荷にしないために、userdir.plのリストを非常に短くします。
#!/ usr / bin / perl -w
Email :: Simpleを 使用し ます。
MIME :: Base64 ( )を 使用し ます。
DBIを使用し ます。
...
#システムコマンドをSQLに変換するためのフィールド名のハッシュ
私の %fnames = (
name => 'name' 、
gor => 'gtel' 、
...
) ;
...
#スクリプトの入力ストリームに送られるメッセージ
#完全に変数に書き込む
while ( <> )
{
$メッセージ = $ _ ;
}
#変数の解析、必要なデータの強調表示
my $ email = new Email :: Simple ( $ message ) ;
私の $ from = $ email- > header ( "From" ) ;
私の $ to = $ email- > header ( "To" ) ;
私の $ subj = $ email- > header ( "Subject" ) ;
...
#手紙の件名で、ユニット識別子を決定します
私の $ sth = $ dbh- > prepare ( "コード=?の div からIDを選択" ) ;
if ( $ sth- > execute ( $ subj ) )
{
if ( defined ( $ div_code = $ sth- > fetchrow ( ) ) )) { $ div_id = $ div_code ; }
}
$ sth- > finish ( ) ;
...
if ( $ div_id ne 'empty' )
{
$ cmd = 〜s / n // ;
#$ cmd =〜tr / // g;
私の @lines = split ( /; / 、 $ cmd ) ;
...
foreach $行 ( @lines )
{
#ここで、コマンドは直接解析されます
ムシャムシャ ( $ライン ) ;
$ line = 〜s / n // ;
if ( $ line =〜 / ^ _ cmd / )
{
私の @fields = split ( /:/ 、 $ line ) ;
私の $ debug_str = '' ;
foreach $フィールド ( @fields )
{
私の @prms = split ( / = / 、 $ field ) ;
if ( $#prms == 1 ) { $ params { $ prms [ 0 ] } = $ prms [ 1 ] ; }
$ debug_str 。= "$ prms [0] = $ params {$ prms [0]};" ;
}
}
#次に、受信したコマンドの処理はすでに発生しています
if ( $ params { '_cmd' } eq 'insert' ) ############################## INSERT cmd
{
私の $ qry = '' ;
私の $ qry_valid = 0 ;
if ( $ params { 'type' } eq 'mbox' ) ########メールボックス
{
$ qry = "人に挿入(" ;
私の $ dfields = '' ;
私の $ fields_cnt = 0 ;
while ( ( $ key 、 $ value ) = each ( %params ) )
{
if ( defined ( $ fnames { $ key } ) && $ params { $ key } ne '' )
{
...
}
}
...
}
elsif ( $ params { 'type' } eq 'alias' )
...
}
...
#次に、残りのコマンドは同様の方法で処理されます
#およびデータベースへのデータの書き込み
}
}
メールアカウントを編集するときにチームを作成し、それを上記の受信者に送信するだけです。 私が自分でWebインターフェースを書いたので、これを行うのは難しくありません。たとえば、afterSaveモデルで使用されるYiiフレームワークのコンポーネントです。
/ *
設定ファイルmain.phpでプロパティを設定するには、この行をファイルに追加する必要があります
'コンポーネント' =>配列(
...
'Userdir' =>配列(
'class' => 'Userdir'、
'divId' => '0001'、
「userdirAddr」=>「admin@company.ru」、
)、
...
)、
* /
クラス Userdir は CApplicationComponentを拡張します
{
private $ _divId ;
private $ _userdirAddr ;
private $ _isLoaded = false ;
private $ _transNames = array (
'name' => 'name' 、
'ユーザー名' => 'eml' 、
'post' => 'post' 、
'phone_gor' => 'gor' 、
...
) ;
private $ _cmdLine = '' ;
//プロパティを操作する
パブリック 関数 getDivId ( )
{
$ this- > _divIdを返します。
}
パブリック 関数 setDivId ( $ value )
{
$ this- > _divId = $ value ;
}
パブリック 関数 getUserdirAddr ( )
{
$ this- > _userdirAddrを返します。
}
パブリック 関数 setUserdirAddr ( $ value )
{
$ this- > _userdirAddr = $ value ;
}
//パブリックメソッド
パブリック 関数のロード( $ attrs 、 $ cmd 、 $ type = 'mbox' )
{
$ this- > _cmdLine = '_cmd =' 。 $ cmd 。 ':タイプ=' 。 $タイプ ;
foreach ( $ attrs as $ key => $ value )
{
if ( isset ( $ this- > _transNames [ $ key ] ) ))
{
$ encodedValue = iconv ( 'UTF-8' 、 'cp1251' 、 $ value ) ;
$ this- > _cmdLine 。= ':' 。 $ this- > _transNames [ $ key ] 。 '=' 。 $ encodeValue ;
}
}
$ this- > _cmdLine 。= "n" ;
$ this- > _isLoaded = true ;
}
パブリック 関数 send ( $ params = null )
{
if ( $ this- > _isLoaded )
{
メールを 返す ( $ this- > _userdirAddr 、 $ this- > _divId 、 $ this- > _cmdLine ) ;
}
trueを 返し ます 。
}
}
afterSaveでは、次のように記述します。
// Userdirレプリケーション
$ userdir = Yii :: app ( ) -> Userdir ;
$ userdir- > setDivId ( $ div_id = Domain :: model ( ) -> find ( "domain = ' $ this-> domain '" ) -> userdir_id ) ;
$ _userdirModes = array (
'phones' => 'update' 、
'更新' => '更新' 、
'作成' => '挿入' 、
'delete' => 'delete' 、
'アクティブ' => '更新' 、
) ;
if ( isset ( $ _userdirModes [ $ this- > Scenario ] ) ))
{
/ *編集* /
$ userdir- > load ( $ this- > attributes 、 $ _userdirModes [ $ this- > Scenario ] ) ;
}
if ( ! $ retCode = $ userdir- > send ( ) ) return $ retCode ;
そして今、すべてが自動的に更新されます。
このシステムは実装され、3年目で正常に動作しています。 私は少し面倒なことを書いて、それをカットしようとしました、誰かが興味を持っているなら、私は質問に答えます。