2024年3月11日月曜日

Excelから変換したPDFが携帯電話で表示されない

管理してるWEBサイトに Excel で作成されたファイルを表示するために、PDFに変換してアップします。よね?
うちもそうしてます。
日本語のファイル名にならないように英数名のファイルにしてアップロード
よし。これで完了、っと。

携帯(iPhone)で見えないとのご連絡!

いや、いつもやってることなのに。
どれどれ、と自分の iPhone で開いてみると・・・

 ほんとだ!
左上1/4くらいしか表示されません!
で、しばらくすると全体が表示されます・・・・
いや、これじゃ、あかんでしょ・・・

PDF に変換する!

Excel でPDFファイルとして保存するには、2つの方法があります。

ひとつは、「ファイル」メニューから「Adobe PDF として保存」を選ぶ方法です。
今回は、こちらの機能を使ってPDFに変換しました。

PCではちゃんと表示されるのにな・・・

別の方法を試してみる

もう一つ。
「ファイル」「名前を付けて保存」で、ファイル名の下のコンボボックスで「PDF(*.pdf)」を選ぶ方法です。

今回のファイルの場合、こちらを使うと、PDFのファイルサイズが少し小さくなりました。

まとめ

2番目の方法で出力したファイルをWEBサイトに公開したところ、iPhone でもサクッと表示されるようになりました。
同じ悩みを経験した場合は試してみてくださいまし。

めでたしめでたし。

2024年3月10日日曜日

WEBアプリ[Enter]キーでフォーカス移動の問題点

HTMLの[Enter]でフォーカス移動

むかしはねぇ。業務系アプリのほとんどで、入力枠で[Enter]押されたら、次の入力枠にフォーカスを移すのって、当たり前でしたよね。
ところがどっこい。HTMLでの[Enter]は submit() がデフォルトになってる。
業務系WEBアプリとしては、[Enter]キーでフォーカス移動は、ユーザーからのご要望も強くありますね。
と、いうことで。
[Enter]キーでのsubmit()をフォーカス移動にしてたのが、次のコードです。

$(function(){
  $("input").on("keydown", function(e) {
    if ((e.which && e.which !== 13) && (e.keyCode && e.keyCode !== 13)) 
    {
      return true;
    }
    else
    {
      var count = $('input').length;
      var current_index = $('input').index(this);
      for(let index = current_index; index < count; index++)
      {
        var next_input = $('input')[index + 1];
        var is_hidden = $(next_input).is(':hidden');
        var is_readonly = $(next_input).prop('readonly');
        var is_button = $(next_input).is(':button');
        if(is_hidden || is_readonly || is_button)
        {
          continue;
        }
        $(next_input).focus();
        return false;
      }
      $('input')[current_index].blur(); 
      return false;
    }
  });
}(this));
textarea に移動しない!と思った人は変更してくださいw

MacOSの[かな]キーでフォーカス移動してしまう!

Mac 使いのお客様から、こんなクレームが来ました!
フォーカスが移動する、ってのなら上記コードが原因のわけです。
と、いうことで。
MacOS 機を入手して動きを調べてみました。

「かな」キーの keyCode が 13 !!!

なんでこんなことしてんですかねーw
数字(と記号の)郵便番号入力から、住所欄の入力に移動して「かな」キーを押すと、住所欄すっ飛ばして「名前」のところにジャンプしてしまうわけですw
『使えねぇ』と思われても仕方ありません。ええ。

keydown を keypress に

  $("input").on("keydown", function(e) {
この部分を

  $("input").on("keypress", function(e) {
に変更することで、対応できました。

まとめ

ちなみに、「かな」キーのkeydownでのkeyCode は 13 でしたが、 keypress での keyCode は 229 が入ってきます。

HTMLって日本語のことちっとも考えてくれてないよね。
IE にあった、IME の制御機能、入れてほしいよね・・・

とりあえず動いた。
めでたしめでたし。


参照
Enterキーを無効にする方法
日本語入力時のキーイベントの仕様変更と対策

2024年2月8日木曜日

CakePHP でアソシエーションを含めたレコードの読込み

containを使え

あちこちのサイトを見ると、contain を使った方法がたくさん書いてありますね。
たとえば、products テーブルに取引先のIDである partner_id が含まれているとき
$product_id で示されるレコードを取得するには、次のように書きます。

  // コントローラの initialize() で
  $this->loadModel('Products');
  // レコードの取り込み処理で
  $record = $this->Products->get($product_id, ['contain' => ['Partners']]);

cakePHP を書きなれている人なら、同じみでしょう。
QueryBuilder を使った find() などでもアソシエーションとともに読み込みできますね。

  $record = $this->Products->find()
    contain(['Partners'])
    ->first();

どんだけ書くねん!

『こう書いておけば動く』から、なんでしょうか・・・
たぶん、世界中の人が、さまざまなプロダクトのテーブル参照を『とっても便利な』QueryBuilder を使って読み込んでいるのでしょうね。

おおきなプロダクトになってくると、それぞれのテーブルごとにレコード参照のコントローラ.phpファイルが増えて、手に負えなくなってしまいますねぇ

簡単にしちゃいましょう

レコードが _id で示されるインデックス指定のフィールドを持っているとき、contain でアソシエーションを『含めずに』読み込む、なんてことは逆に少ないはずです。
少なくとも、私の担当するプロダクトではレコード読込でアソシエーション不要、なんてことは皆無に近いですね。
そこで、こんなコードを共通コンポーネントに書き加えました。

/**
 * 指定テーブルのアソシエーション(BelongsTo/BelongsToMany)を含めて
 * レコードを取得する
 */
function LoadRecordWithBelongs($table, $id)
{
  $record = false;
  $contains = [];
  $associations = $table->associations();
  $items = $associations->getByType(['BelongsTo', 'BelongsToMany']);
  foreach($items as $item)
  {
    $name = $item->getName();
    $contains[] = $name;
  }
  $record = $table->get($id, ['contain' => $contains]);
  return $record;
}
ようするに、belongsTo, belongsToMany のアソシエーション定義されているテーブルなら、そいつをまとめて読み込んでくれよ、というコードです。

まとめ

共通化することで、たくさんのソースを削除することができるようになりますが。
それは、これからやり始めようとしてるところです・・・w

めでたしめでたし。

2024年2月3日土曜日

Visual Studio Code でCentOS7のリモートデバッグができなくなった!

突然使えなくなった VS Code + Remote SSH

昨日まで、普通に使えていた VS Code と Hyper-V 上の CentOS 7.9 上でのリモート開発
今朝、突然、使えなくなりましたよ。
どうやってもつながらない!
今日は土曜日だから、月曜あたりは日本中でこの問題、世界中で騒がれるんじゃないでしょうかね・・・
ったく・・・

パスワード入力してログインしようとすると、エラーになって繋がりません。

glibcとlibstdc++のバージョンが古い?

OSの中身を見てみると、確かに古いようです。
で、それならバージョンアップしてやろうということで、やってみました。
バージョンアップでやってみたことは、次の通り。
・gcc のバージョンアップ 13.2.0 をインストール。2度失敗。長かった・・・
・ここで、libstdc++を入れ替えできました。
・次に glibc のバージョンをあげる件ですが、結論から言いますと失敗しました。
・python 3 のインストール
・yum が動かなくなるので yum 編集
・gmake が古いと怒られます。で、4.2あたりをインストールしようとすると、エラーで止まっちゃう!
と、いうことで、CentOS7 側での対応はできませんでした。
(6時間・・・返してくださいよ・・・(´;ω;`))

解決方法

要するに、Visual Studio Code のアップデートが原因なんですよ
January 2024 (version 1.86)
拡張機能の Remote SSH がアップデートされてました。
で、このアップデートを古いバージョンにして、CentOS 側の /home/ユーザー ディレクトリ上の .vscode-server ディレクトリを削除。
・・・問題は変わりませんでした。
納期すぎていて、とっても焦っているのに、丸一日潰れかかってます・・・どーしてくれるんだ!

対応策

VS Code のバージョンをダウングレードしてしまいます!
まず、現在の VS Code をアンインストールする前に、次を実行しておきます。
・VS Code の自動アップデートを停止しておく。(設定:update あたり)
・拡張機能の自動アップデートを停止しておき。
VS Code をアンインストール。
このページの Downloads ってとこから該当バージョンをダウンロード
インストールしましょう!
CentOS 側の /home/ユーザー ディレクトリ上の .vscode-server ディレクトリを削除もわすれずに。

動いた!

昨日まで使っていたワークスペースを開いてみると、無事パスワード入力後に編集できるようになりました。
めでたしめでたし。

2024年1月28日日曜日

Hyper-V に Ubuntu 22.04.03 をインストールしようとして・・・

なんども失敗したので、記録しとく。

いつものとおり、Ubuntuサイトから .iso ファイルをダウンロードして
いろいろと失敗したので。

cloud-init で停止してしまう

上段に[Install Complete!]がでたんで[Reboot]を選択したら、真っ黒画面になってしまって、一晩放置してもNGだった。
再度、インストールを試みてみると、ログで reached cloud-init のところで止まったまま。(これも一晩放置したけどダメ)
cloud-init を無効にする方法はネット上にあるけど、そもそもログインする前に死ぬので、どーしようもない
※このVM、Hyper-Vでは起動中のままになってしまって、削除することもできなかったよ。
Hyper-Vサービスを停止⇒PC再起動⇒VMファイルを削除⇒Hyper-Vサービス起動で削除できました。

そこで。ネットワークにつながない状態でインストールすればいいんだ!ということに気づいたわけです。 Hyper-V のネットワークアダプタを接続せずに新しい Ubuntu をインストールすることで、ようやくログイン画面にたどり着きました!

インストール後の注意点

Ubuntu の説明サイトを見ると、sudo コマンド経由の説明ばっかりで、めんどくさいですよね・・・
あちきは、もっぱら $ sudo su - を実行して root 権限に移動してから処理してます。

まだネットワークアダプタがない状態で、次をやっておきます。
cloud-init を無効化

  # touch /etc/cloud/cloud-init.disabled
  
これでネットワークつないで起動しても cloud-init で止まることはない(はず)

ついでに、起動高速化のため、あちこちに書いてある grub timeout を変更しておきます。

  # sudo gedit /etc/default/grub
  # GRUB_TIMEOUT=2
  # sudo update-grub
  

ネットワークを設定します。
# ip link でネットワークアダプタ名を取得します (ここでは、eth0)
で、ネットワーク設定の yaml ファイルを編集します。(IPアドレスは、ご自分の環境に合わせてくださいまし)

  # mv /etc/netplan/00-installer-config.yaml  /etc/netplan/00-installer-config.yaml.disable
  # vi /etc/netplan/01-installer-config.yaml
  network:
    version: 2
    renderer: networkd
    ethernets:
      eth0:
        dhcp4: false
        addresses: [192.168.0.90/24]
        routes:
          - to: default
            via: 192.168.0.1
        nameservers:
          addresses: [192.168.0.1]
  

ここで一度シャットダウン。Hyper-V の設定でネットワークアダプタを接続して起動します!
・・・真っ黒画面になってしまうかもしれません(実際、大汗でしたw
2分ほど待つと、boot シーケンスが動作し始めますので、我慢してください(笑)
ネットワークに繋がれば、TeraTerm でSSHログインできます。

真っ黒画面対策として、パッケージ関係のアップデートを行います。

  # apt update
  # apt dist-upgrade
  # apt autoremove
  

再起動してみましょう!

  # reboot
  
さくっと起動するようになりました。

blk_update_request: I/O error

Hyper-V コンソールを表示させると、しょっちゅう blk_update_request: I/O error が出力されます。
これは、フロッピーディスクがないってエラーのようです。 よそのサイトを参考に、以下の方法でエラーをなくすことができました。

  ▼ blacklist に登録
  # echo "blacklist floppy" >> /lib/modprobe.d/dist-blacklist.conf
  ▼ カーネルから floppy を削除
  # rmmod floppy
  ▼ dracut がなかったのでインストール
  # apt install dracut-core
  ▼initramfs の再構築をする
  # cd /boot/
  # dracut -f -v
  ▼再起動
  # reboot
  


次からは、失敗しないぞ!w
めでたしめでたし。

参考にしたサイト
https://x.momo86.net/article/154
https://qiita.com/n_mikuni/items/bf94289eaff93b82ac27

2024年1月27日土曜日

仮想マシンサービスは利用できません

Hyper-V マネージャでサービスを停止してしまった

どうしても止められなくなったVM(Ubuntu)を再構築しちゃおうと思ったんだけど、できなくて
いろいろやってるうちに、「サービスの停止」を選んでしもうた!(笑)
サービスを停止させた Hyper-V マネージャを復活させる方法について。

Hyper-Vのサービスを停止させてしまうと

こんな画面になっちゃって、仮想マシンが表示されなくなってしまいます。

Hyper-V サービスを復活させるには

PowerShell を「管理者モード」で起動します!

サービスを自動起動に設定します。

PS C:\WINDOWS\system32> sc.exe config vmms start=auto

[SC] ChangeServiceConfig SUCCESS

次に、サービスを開始します。

PS C:\WINDOWS\system32> sc.exe start vmms

SERVICE_NAME: vmms
        TYPE               : 10  WIN32_OWN_PROCESS
        STATE              : 2  START_PENDING
                                (NOT_STOPPABLE, NOT_PAUSABLE, IGNORES_SHUTDOWN)
        WIN32_EXIT_CODE    : 0  (0x0)
        SERVICE_EXIT_CODE  : 0  (0x0)
        CHECKPOINT         : 0x0
        WAIT_HINT          : 0x0
        PID                : 39080
        FLAGS              :

Hyper-V の仮想マシンが復活してくれます


めでたしめでたし。

2023年7月26日水曜日

WEBサーバで、一定時間だけ有効な認証キーで登録する

メールアドレスの入力確認のための方法

2度入力させるUIって、ほんとダメですよね。

メールアドレスを入力してください。(*)
確認のため、再度メールアドレスを入力してください。(*)
(*)必須項目。しかもコピペできないw

んでも、入力ミスっていたら、その後メール来ないし。(笑)
これ、よく見かけますよねぇ。
昔はうちもやってましたが(笑)

認証キーを送信して、入力させる方法

そこで、30分とか1時間だけ有効な認証キーを送って、それを入力してもらうようにするといいんですね。
これも、よく見かけるようになりましたが(笑)

こんな画面です。

メールアドレスを入力して「認証キー送信」ボタンを押してもらう。
受信したメールに記載の認証キーを入力して、本来の登録処理にする。
こうすることで、メールが届かなかったら、入力ミスだとわかるし、メールが届けば間違いないメールアドレスだと判断できますね
認証キーは一定時間(たとえば30分)だけ有効で、それを過ぎると無効になります。

で、これをどのようにサーバー側で実装しようか、ということですよ。

HTMLページ上の Ajax を使った Javascript は以下のような感じになります。

// 認証キーを送信する処理
onButtonSendAuthKey = function()
{
    // メールアドレスに認証コードを発行するサーバープログラム
    var url = '/regist-temporary';
    var params = {
        mail: $('#input-mail').val()
    };
    $.post({
        url: url,
        data: params,
    }).then(function(response){
        // 略
    });
}

// 入力されたメールアドレスと認証キーで登録する処理
onButtonEntry = function()
{
    // メールアドレスを登録するサーバープログラム
    var url = '/regist-user';
    var params = {
        mail: $('#input-mail').val(),
        auth_key: $('#input-auth_key').val()
    };
    $.post({
        url: url,
        data: params,
    }).then(function(response){
        // 略
    });
}

30分有効な認証キーをどう管理する?

ランダムな認証キーは乱数で構成すれば簡単にできますが、作成した認証キーをどのように管理するのか?
というのが今回のテーマです。
Session と Cookie を使って、ブラウザ側に記憶させる、という方法もあるでしょうけど、クライアント側に覚えさせるのは、ちょっと抵抗を感じてしまいます。
んでは、サーバー側で対応するとすると、どうすべきでしょう?
データベースに一時ユーザーテーブルを用意しておく、という方法が一般的のようです。

一定時間を経過したら、その情報を削除しなければならないわけで、それには cron でシェル作ってDBの作成時間を見て delete、と。
と考えるとですね、おっさんプログラマにはリソースの膨大な無駄遣いのように思えて仕方がないのですよ(笑)

サーバー側の認証キー送信処理(/regist-temporary)では以下の手続きが必要になります。
・認証キーを生成する。
・メールアドレスに認証キーを送信する。
・メールアドレスに対して発行した認証キーを記録する。
・30分経っても登録されない場合、認証キーを削除する。
サーバー側の登録処理(/regist-user)は以下の手続きが必要です。
・認証キーが無効になっていないかを判断する。
・メールアドレスと認証キーが一致するか判断する。
・本来の登録処理を行う。
という流れなんですが、リソースの使用を最小限に留めて、サクサク動くようにできないもんでしょうかね。

/dev/shm を使う!

そこで、思いついたのが、一般的な Linuxサーバに必ず用意されていて、あまり利用されている風ではない、/dev/shm (RAMディスク)を利用する方法です。
1.ユーザーごとに生成した認証キーを /dev/shm/サービス名/temp_auth/ ディレクトリに記録しておく。
2.登録時には、記録内容を照合する。
3.cron で記録ディレクトリの一定時間経過したファイルを削除する。
という方法ですね。

メールアドレスごとに認証キーを生成して、RAMディスクに記録する

// メールアドレスごとの認証キーを生成して、送信する処理
function send_auth_key($mail)
{
    // ランダムな認証キーを生成
    $auth_key = $this->create_auth_key();
    $path = '/dev/shm/service/temp_auth';
    $rc = mkdir($path, 0777, true);
    if(!$rc)
    {
        //エラー処理
    }
    //メールアドレスを Base64(URL)エンコード
    $mail_hash = $this->Base64UrlEncode($mail);
    // 拡張子に .key を追加(なんとなくw)
    $fname =  $mail_hash . '.key';
    $filename = "$path/$fname";
    $rc = (0 < file_put_contents($filename, $auth_key));
    if($rc)
    {
        $rc = chmod($filename, 0666);
        if($rc)
        {
            // 認証キーをメールで送信(省略)
            $rc = $this->send_auth_key($mail, $auth_key);
        }
    }
}

// ランダムな認証キー生成
function create_auth_key($column = 6)
{
    $authKey = '';
    $digits = '0123456789';

    for ($i = 0; $i < $column; $i++) {
        $authKey .= $digits[rand(0, strlen($digits) - 1)];
    }

    return $authKey;
}

// Base64URL encoder
function Base64UrlEncode($data) { 
    return rtrim(strtr(base64_encode($data), '+/', '-_'), '='); 
} 
  
// Base64URL decoder
function Base64UrlDecode($data) { 
    // = 埋めしなくてもいける
    return base64_decode(str_pad(strtr($data, '-_', '+/'), strlen($data) % 4, '=', STR_PAD_RIGHT)); 
} 

メールアドレスを BASE64エンコードするのは、メールアドレスの中にファイル名に使用できない文字が含まれるかもしれないのを避けるためですね。
BASE64で利用される +と/ の記号は -と_ に変換することで、ファイル名として機能するようにしています。
あと、chmod 0666 しておくのは、apache と cron でアクセスしてくるユーザーが異なるためです。

一時ファイルを一定時間後に削除する、のはcron で以下のように設定しました。(例は3分ごとに実行してるので、少しタイムラグあります)

#crontab で認証キーの制限時間超えたファイルを消す
*/3 * * * * find /dev/shm/service/temp_auth/ -name '*.key' -mmin +30 -delete > /dev/null 2>&1

実際に認証キーとメールアドレスが送られてきたときの処理コードを入れておきます。

function checkAuthKey($mail, $auth_key)
{
    $rc = false;
    $error_message = '認証キーが異なっているか、または期限が切れています';
    $path = '/dev/shm/service/temp_auth';
    $mail_hash = $this->Base64UrlEncode($mail);
    $fname =  $mail_hash . '.key';
    $filename = "$path/$fname";
    $saved_key = false;
    if(file_exists($filename))
    {
        $saved_key = file_get_contents($filename);
    }
    if($saved_key)
    {
        $rc = (strcmp($auth_key, $saved_key) == 0);
    }
    return [$rc, $filename, $mail_hash, $error_message];
}

まとめ

今回はサーバー側で一時的な認証キーを生成してメールアドレスの確認と登録処理に至るプロセスで、Cookieやデータベースを使わない方法をまとめてみました
こういった需要って、よくあることだと思うので、参考にしてくださいまし。
実際、サクサク動いてくれるし間違いも起こりにくいので、安全に運用できています。
めでたしめでたし。