プログラミンGOO

プログラミングナレッジ、ワードプレス、広告収入等について、気づき・備忘録を残していきます。

UNIXコマンド備忘録

パワーシェルでやる場合
https://qiita.com/mima_ita/items/ae31f3a19389e69b307f

基本コマンド

dir: 同階層のファイル一覧を表示
Ctrl + l:プロンプトをリセット EscでもOK プロンプトではcls
pwd:現在のディレクトリを表示(print working directory)
cd -:直上のディレクトリに戻る
Ctrl + R →キーワード:キーワードを含む直近のコマンドを検索
history:コマンドの履歴を表示
→!行数:行数のコマンドを実行
!!:直前のコマンドを実行
!-2:2つ前のコマンドを実行
!ab:abから始まる直前のコマンドを実行 ※『:p』を最後に着けると実行せず表示
cd !$:cd ひとつ前のコマンド で実行
mkdir --help:mdkirのヘルプを表示 ※man mkdirでマニュアル
ls:リスト表示
-l:パーミッション・所有者情報も含め
-a:隠しファイル含め

ファイル作成

type nul > ファイル名	windows?

ファイルの内容を閲覧

cat ファイル名

lessコマンド ※ファイルの内容を表示してスクロール可能な状態にする

less ファイル名 //起動
『:』のあとに以下のコマンドで状態を変更
q //終了
/all //文字列『all』を検索

ファイル操作

cp ファイルA ファイルB //AをBにコピー
-r //再帰的にコピー
-f //上書き
-i //上書き確認
mv (ファイルA) (ディレクトリB):移動
mv (ファイルA) (ファイルB):ファイル名変更

rm (ファイル名):削除 ※ディレクトリにも使える?
-r:中身があっても削除
rmdir(ディレクトリ名):削除 ※中身が空の場合のみ

■フォルダ作成

mkdir ディレクトリ名
-p //記載したパスのディレクトリをすべて作成

■ファイル作成

touch ファイル名 //空のファイルを作成(本来は既存ファイルの日付更新)

複数ファイルを作成
参考:ターミナルの作業が捗るかもな小技5つ - Qiita

touch {dir1,dir2}/{file1,file2}

■ファイルのパーミッション変更

chmod a+w //ファイル名

・オプション
a //すべてのユーザ
u //所有者
g //グループ
o //他のユーザ
r:読み、w:書き、x:実行

■ファイル・ディレクトリの所有者とグループを変更

chown ユーザー名 ファイル名

■ディレクトリ内にある文字列を指定して検索

find ディレクトリ -name 文字列

grep (検索文字列) (ファイル名):指定パターンの文字列を検索

■ファイルの編集 :vim
※linuxについてるエディタ
※ファイルがなければ保存した際に新規作成される

vi hello.txt	//vi ●ファイル名●
i	//編集モードに切り替え ※insertの略
Escキー	//編集モード終了
:w	//保存
:q	//vim終了
※編集カ所がある場合はエラーになる。
編集を破棄して終了する場合は
:q!
:wq	//保存して終了
ファイルの内容を確認したい場合は
cat ●ファイル名●	//スクロール可能
または
less ●ファイル名●

・その他コマンド
参考:https://qiita.com/colorrabbit/items/755cfbb0e97d48280775

dd	//行切り取り
p	//貼り付け
.	//直前のコマンドを繰り返す
Ctrl n	//補完
u	//undo
Ctrl r	//redo

【エラー対応ログ】

■warning: LF will be replaced by CRLF
『LFをCRLFに変換するよ』という意味。
LF、CRLFは改行コード

hpでFn(ファンクション)キーが効かないときの対処法

Fn(ファンクション)キーが普段の動作と反転してしまっていたので修正。
ちょっとググったのですが僕のhp(ヒューレットパッカード)のPCと仕様が違うのか解決手順が違ったのでメモ。
ちなみに僕の機種は『EliteBook 830 G8』でした。

他のサイトでよく紹介されているようにBIOS設定で変更するところは一緒です。

Fnキーの挙動修正手順

  1. Shiftキーを押しながら再起動
  2. オプションの選択>トラブルシューティング>詳細オプション>UEFIファームウェアの設定>再起動
  3. BIOS Setup>System Options
  4. Advancedタブに移動
  5. Launch Hotkeys without Fn Keypressを『disable』に設定

postgreSQL備忘録

【参考】
公式サイト
https://www.postgresql.org/

★dkt_searchの初期DB作成についてはSpringSecurityのドキュメントを参照

【管理コマンド基本操作】

・ログイン

psql -U postgres  //管理者 ※本来はユーザ(ロールという)を作成してそちらを使用すること
\l //データベース一覧
\c databateName	//データベースに移動 
\dt	//table一覧
\d tableName	//table詳細
select * from TABLENAME;	※ここからコロンが必要なので注意
update tableName set columnName = value where …

・その他のよく使うコマンド

\d	tableName	//table詳細
\l	//db一覧
\q	//quit
\x	//拡張表示on,off
\?	//よく使うコマンド一覧を表示

【DB基本操作】

・値の追加

insert into tableName (columnName, columnName2) values ('clmvalue', 'clm2value'), ('clmvalue2', 'clm2value2');

・抽出

select * from tableName;

・更新

update tableName set columnName = value where …

・行削除

delete from tableName where …

【データ】

数値	: integer(int), real, serial
文字	: char(5), varchar(255), text
真偽	: boolean TRUE FALSE t f
日付	: date, time, timestamp

【構造変更】

■カラム追加

alter table tableName add column columnName dataType;

■カラム削除

alter table tableName drop columnName;

■リネーム

alter table tableName rename to newColumnName;

型変更

alter table tableName alter columnName type newType;

booleanとsmallintなど、扱うタイプが違う場合、『この時はこれ』と条件を明示しないとエラーになる
https://qiita.com/seiro/items/ade4c220dfe4acb0ef4b
※smallintをbooleanに変更

ALTER TABLE users ALTER enabled TYPE boolean
  USING CASE
    WHEN enabled = 1 THEN TRUE
    WHEN enabled = 0 THEN FALSE
    ELSE null
  END;

【テーブル】

テーブル作成

create table tableName (columnName varchar(255), columnName2 text)

名前変更

alter table tableName rename to newTableName

テーブル削除

drop table tableName

【値の取得】クエリ

■基本

select * from tableName where id = 1;

■関連(バインド)

select tableName.columnName, tableName2.columnName2 from tableName, tableName2 where (例) tableName.id = tableName2.foreing_id;

省略形 ※上記と同義

select tn.columnName, tn2.columnName2 from tableName tn, tableName2 tn2 where (例) tn.id = tn2.foreing_id;

【制約】

not null
unique
check(length(columnName) > 5)	//ほかにもいろいろ
default	//例)created timestamp default 'now', is_draft bolean default TRUE
primary key (not null, unique)

・UNIQUE制約について
MySQLはUNIQUE KEYだが
PostgresqlではUNIQUE INDEX またはUNIQUE制約
UNIQUE制約はカラムの制約なので簡単につけ外しはしないので、基本的にはUNIQUE INDEX?
参考:https://okwave.jp/qa/q6326929.html

■外部キー制約
子テーブルのカラムに外部キーを追加することで、親テーブルとの関係性を持たせる。
・外部キーの追加

ALTER TABLE "child" ADD CONSTRAINT "keyName" FOREIGN KEY (parent_id) REFERENCES parent(id) ON DELETE CASCADE;"

childテーブルのparent_idカラムにkeyNameという名前の外部キーを追加する。
このカラムはparentテーブルのidを参照する。
ON DELETE CASCADE:親オブジェクトが変更された場合の処理をどうするかを設定する。この設定では削除処理が行われた場合に子オブジェクトも削除する。

・外部キーの削除

ALTER TABLE "users" DROP COMSTRAINT "users_to_account";

・外部キー制約・参照元の確認

\d tableName

・読み方

外部キー制約:
    "keyName" FOREIGN KEY (client_id) REFERENCES client(id)

親テーブルがあり、そこに向かって外部キー参照をしていることを示す。※自身は子テーブルである
このテーブル(子)のclient_idというカラムに、keyNameという外部キーが設定されているよ。このカラムはclient(親)テーブルのidカラムを参照しているよ。

参照元:
    TABLE "schedule" CONSTRAINT "keyName" FOREIGN KEY (client_id) REFERENCES client(id)

子テーブル(schedule)があり、そこから外部キー参照されていることを表す。※自身は親テーブルである
scheduleテーブル(子)のclient_idカラムから外部キー参照されているよ。外部キーはこのテーブル:clientテーブル(親)のidカラムを参照しているよ。

【インデックス】

インデックスを付けることで大量のデータを処理する際に処理速度を向上させることができる。
インデックスとは付箋。例えば規則性のある商品番号などに設定すると効果的。
逆に、カテゴリーなど、カラムとして管理してしまった方が良いものはインデックスではなくカラムにするほうが適切。
また、インデックスをつけすぎると結局規則性を検索できなくなるため逆効果になることもある。

create index indexName on tableName(columnName);
drop index indexName;	//削除

【ユーザ】

ユーザの作成

https://qiita.com/wb773/items/248e6e083b2fe12e820a

ルートユーザログイン
>psql
>CREATE ROLE role_sample WITH LOGIN PASSWORD 'password';
※最後のセミコロンを忘れると登録されないので注意

*** パスワード設定・変更
alter role postgres with password 'passName';

【ビュー】※よく使うsql文を登録しておく機能

作成

create view viewName as sql;

呼び出し

select * from viewName;

削除

drop view viewName;

ビュー一覧表示

\dv

【トランザクション】

・途中でselect文で確認することもできるよ

開始

begin;

終了

commit;

中止

rollback;

【postgresql管理用コマンド】

・ヘルプ

help

■データベース一覧

psql -l

■データベース作成

createdb dbName

//★このへんはsql文ではなくpostgresqlの管理用コマンドでcreate databace dbNameのラッパー
//なので普通にコマンドプロンプトで実行するとエラーになる。
//その場合は普通にsql文で実行しよう
https://oshiete.goo.ne.jp/qa/2052007.html

※ホスト名の調べ方

ipconfig /all

削除

dropdb dbName

接続

psql dbName

~Mysqlとの違い~
・『`』(バッククォート) :全て不要
・SET AUTOCOMMIT = 0; :\set autocommit = 0FF; ※大文字小文字が区別されるので注意
・tinyint(1) :smallint
・ALTER TABLE `authorities` ADD UNIQUE KEY `username` (`username`,`authority`);
:CREATE UNIQUE INDEX username ON authorities (username,authority);

【appendix】

■バージョン

psql --version

■外部ファイルからインストール

\i fileName

【導入】

■postgresQLのインストール
参考:https://qiita.com/tom-sato/items/037b8f8cb4b326710f71

※PSQLも一緒にインストールされる
C:\Program Files\PostgreSQL\13\bin のpslq.exe

■パスを通す
C:\Program Files\PostgreSQL\13\bin で登録しておく

■コマンドプロンプト再起動

■バージョン確認

psql -V

■コマンドプロンプトからpsqlを使用
・環境変数のPathを通す
C:\Program Files\PostgreSQL\12\bin
※psql.exeがあるフォルダ

・ログイン

psql -u postgres -d postgres


■パスワードを忘れた
サーバの操作に使用 :pg_ctl
-D :ディレクトリ指定 ※どこを指定すればいいかわからん
https://www.postgresql.jp/document/10/html/app-pg-ctl.html

https://qiita.com/ritya/items/b1ae186f3f6308c52289
こっちは一瞬で出来た

・再起動は以下でできた

pg_ctl restart -D "C:\Program Files\PostgreSQL\12\data"

GUIで再起動する場合 ※これで-Dのパスも調べられた
スタートボタン右クリック>サービスとアプリケーション>サービス(時間かかる)>postgresql
ここでパスとか停止起動で再起動
https://www.dbonline.jp/postgresql/install/index4.html

・原因、なぜか管理者ではなくユーザ名でログインさせられていた。どゆこと…
psql -U postgres で管理者ログイン
パスワードは初期設定したもの。

【エラー対応】

■列●●は存在しません
updateコマンドを実行したがなぜか値が更新できない
update client_media set media_type = “t” where id = 49;
『列tは存在しません』という謎のエラーが出る。

>>SQLでは文字列はシングルクォーテーションで囲うこと

■三角関係を持つテーブル
クライアント>スケジュール、スケジュール>ジャンル、ジャンル>クライアントというような三角関係を持つテーブルをOneToOneで作成したらデータがうまく更新されなかった。
そもそも論だが、テーブル設計において、三角関係になる構造は作らないのが基本らしい。
今回のような処理もそうだが、取れるデータもどこから取るか?で変わってきてしまうことがあるらしい。
参考:データベース設計の際に気をつけていること - 食べチョク開発者ブログ

thymeleaf備忘録

Java+Springboot+thymeleaf

■概要
1.SpringフレームワークでModelインターフェースで受けた値(フィールド)を
HTML(ビュー)側に簡易実装するための仕組み(テンプレートエンジン)

■準備(宣言)
htmlタグに以下を記述する

//urlは特に意味はなくuri(一意)であることを示しているだけ
※これをしなくても動作はするが、STSで認識されないので宣言をしていく
※xmlnsはxml name spaceの略

■留意事項
・thymeleafは入出力を担当するテンプレートツール。
・基本的にはif分岐やリスト操作など、論理演算は内部で済ませ、必要なモノは加工済みの状態で用意しておく。
・viewから送ることができる値はStringのみ。例えば以下のようにオブジェクトを送ろうとしても、Stringとして扱われるため受け取ることができない。

<input type="hidden" name="attrname" th:value="${obj}">

【ビューへの出力】

~Controller~

@GetMapping("/client")
public String createInfo(
    Model model) {

  Object = new object;
  String sample = "sample"
  
  model.addAttribute("example", sample);
  model.addAttribute("obj", obj);
  
  return "client";
}

~view~

<p th:text="${example}"></p>	//exampleはmodelで設定した属性名を指定

オブジェクトのフィールド(プロパティ)を出力する場合

<p th:text="${obj.attribute}"></p>

または

<div th:object="obj">
  <p th:text="*{attribute}"></p>
</div>

【基本】

文字操作

■文字連結(リテラル置換・パイプライン構文)
https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html#literal-substitutions
以下の2つは同義

<span th:text="|Welcome to our application, ${user.name}!|">
<span th:text="'Welcome to our application, ' + ${user.name} + '!'">
配列・リストの扱い

■参考:https://www.thymeleaf.org/doc/tutorials/2.1/usingthymeleaf_ja.html#%E3%83%9E%E3%83%83%E3%83%97

■値の取り出し
RowMapperなどでDBのリストを作成して出力する場合 :th:each

<tr th:each="inquiry : ${inquiries}" th:object="${inquiry}">	//変数名inquiries : ${ModelにセットされているListオブジェクト}
								//紐づいてinquiryもmodelに登録される?
            <td th:text="*{name}">名前</td>
            <td th:text="*{age}">年齢</td>
            <td th:text="*{gender}">性別</td>
            <td><a th:href="@{|/inquiry/*{id}|}">詳細</a></td>	//後述
</tr>

■リストのn番目の要素を取得
例)Listplansがあり、1番目の要素を取得する。
 ※PlanのフィールドでplanNameがあれば以下のように取得できる。

<input type="text" th:value="${plans.get(1).planName}">


■リストを区切る

th:text="${#strings.listJoin(productList.![id],',')}"

productListのid項目を「,」で区切って並べる
例) productList[x].id -> 「100」「150」「200」
text="100,150,200"

■リストオブジェクトユーティルメソッド

th:if="${#lists.size(listname)}>1"	//『lists』であることに注意
th:if="!${#lists.isEmpty(listname)}"
繰り返し処理 th:each

modelにStringの文字列を格納したリスト"areaNameData"が登録されているとき
リストの文字列一覧を出力したい場合

<span th:each="a : ${areaNameData}">	//areaNameDataの中身を一つずつaに代入 ※aはmodelに格納される?
	<span th:text="${a}"></span>	//aはStringなのでth:textで呼び出し
</span>

・if条件で出しわけたい場合はth:blockでくくる

<th:block th:each="genremap : ${categoriesGenresClientsMap.get('ヒップホップ系')}" >
<tr th:if="${#lists.size(genremap.getValue())} != 0">
	<td th:text="${genremap.getKey()}"></td>
	<td><span th:text="${#lists.size(genremap.getValue())}"></span>校</td>
	<td><input type="checkbox" name="genreNames" th:value="${genremap.getKey()}"></td>
</tr>
</th:block>

■改行やdivなど、ブロック単位で出力する :th:block
空のタグが発生しないのでとても便利

<div th:object="${inquiry}">
  <th:block th:each="line : *{contents.split('\n')}">
	//split():Stringクラスメソッド。
	//inquiryオブジェクトのcontentsを(\n)で分割し
	//変数lineにリストにして格納
	//lineがStringでなくオブジェクトだと.split()は使用できない
    <span th:text="${line}"></span><br>
//その場合は<br>などで調整するしか
  </th:block>
</div>
マップMapの扱い

■th:eachのネスト
https://teratail.com/questions/5133

■mapのメソッド

${map.get(key)}	//キーから値を取得
${#maps.size(mapName)}	//マップの要素数
${#maps.isEmpty(map)}	//空かどうか

//キーや値がマップに含まれているかどうかをチェック
${#maps.containsKey(map, key)}
${#maps.containsAllKeys(map, keys)}
${#maps.containsValue(map, value)}
${#maps.containsAllValues(map, value)}

■実装例
~Java~

Map<Integer, String> SampleMap(
  Model model) {
  
  Map<Integer, String> sampleMapA = new HashMap<>();
  sampleMapA.put(1, "sample");
  sampleMapA.put(2, "sample2");

  Map<Integer, String> sampleMapB = new HashMap<>();
  sampleMapB.put(1, "サンプル");
  sampleMapB.put(2, "サンプル2");

  model.addAttribute("sampleMapA", sampleMapA);
  model.addAttribute("sampleMapB", sampleMapB);
}

~HTML~
//例1

<div th:each="smA : ${sampleMapA}">
  <p th:text="${smA.getKey()}"></p>  //キーを表示:1, 2
  <p th:text="${smA.getValue()}"></p>  //値を表示:"sample", "sample2
</div>

//例2
<div th:each="smA : ${sampleMapA}">
  <div th:each="smB : ${sampleMapB}">
    <p th:if="${smA.getKey()} == ${smB.getKey()}" th:text="${smA.getValue()}"></p>
        //キーが一致したときにsampleMapAの値を表示
  </div>
</div>

//例3
<div th:each="smA, smAStat : ${sampleMapA}">
  <p th:if="${smAStat} == ${#maps.size(sampleMapA)}" th:text="${smA.getValue()}"></p>
    //sampleMapAのStat(何番目に出力されたか)が、
    //sampleMapAの要素数(2)と一致したときに値を表示
</div>

例3のように、MapやListはStat、つまりMapやListが今何番目の要素を表示しているか?といっオブジェクトの情報を使用してロジックを組むこともできる。

■ListをMapにネストする
~Java~

Map<String, List<String>> areaNameWithClientName;	//Map<エリア名, List<エリアに属するクライアント名一覧>>

~HTML~

<div th:each="a : ${areaNameWithClientName}">
  <h3 th:text="${a.getKey()}"></h3>	//Mapは.getKey()でキー名を取得できる
  <table>
    <tr th:each="b : ${a.getValue()}">	//Mapは.getValue()で値を取得できる
                                        //MapのvalueはList<>になっているのでさらにth:each
      <td th:text="${b}"></td>
    </tr>
  </table>
</div>

■th:eachの中でlabel id for

<span th:each="var : arr">
    <input type="checkbox"th:id="${#ids.seq('id_sample')}" th:value="${var.key}" />
    <label th:for="${#ids.prev('id_sample')}" th:text="${var.value}" />
</span>

#ids.prevは#ids.seqを参照して設定される
th:fieldを設定した場合は自動的に#ids.seqが行われるため以下のように書くことができる

<span th:each="var : ${CL_ROLE}">
    <input type="checkbox"th:field="*{rols}" th:value="${var.key}" />
    <label th:for="${#ids.prev('rols')}" th:text="${var.value}" />
</span>
URL・リンク処理

実際のURLはドメインが先頭に入るため、th:href=@{●ULR●}でくくって渡してあげることでthymeleafが補完してくれる。
中に記述するURLはControllerのGetMapで定義しているパスをそのまま記述するものと考えてよさそう。

参考:
https://qiita.com/rubytomato@github/items/ac65c2203d16d1a1bbd7
https://ts0818.hatenablog.com/entry/2017/10/09/144626 //GETではREST形式?

■クエリ形式とREST形式がある。 ※★GETで渡す場合はREST形式が必須??
・クエリ形式:URLパラメータ(『?』の後)として渡す

<a href="/user/profile" th:href="@{/user/profile(id=${'ab123'},role=${'admin'})}">profile</a>
→<a href="/user/profile?id=ab123&amp;role=admin">profile</a>

・REST形式:URL文字列として渡す

<a href="index.html" th:href="@{/user/profile/{id}/{seq}(id=${'abc'},seq=${'1000'})}">profile</a>
→<a href="/user/profile/abc/1000">profile</a>

※前半に{変数名}を指定し、後半の()内で変数に値を代入するテンプレートリテラル
 固定値でなく変数を使用する場合はクオーテーションは不要
 例:変数abc id=${abc} //id=${‘abc’} ではない

■idごとのリンクを作る :th:href ※上記サンプルコード参照

<td><a th:href="@{|/inquiry/*{id}|}">詳細</a></td>	//『|』はパイプライン構文と呼ばれるもの

@{ }の中の文字列であっても${ }や*{ }のように先頭の記号は必要になる。

if分岐

・"${条件式}" 条件式がtrueの場合に表示される
・正反対の挙動を実装したい場合はth:unlessに置き換えるだけで実装できる

<div th:if="${#lists.isEmpty(planDetails)}">※プランが登録されていません</div>
<p th:if="${client.plan} == null">プランが登録されていません</p>
<p th:unless="${client.plan} == null">プランが登録されています</p>

・演算子
以上:ge、 以下:le、 より多きい:gt、 より小さい: lt
等しい:eq、 等しくない:neq
以外:not、
且つ:and、 または:or

<div th:if="${person.intValue ge 20 and person.intValue lt 40}"><img src="/images.png" /></div>
https://arakan-pgm-ai.hatenablog.com/entry/2017/03/14/224244

・演算、リスト、文字列操作

th:if="${#strings.isEmpty(str)}"	//strが空、nullの場合にtrueになる。事前にtrim()されるので空白もtrue。
 th:unless="*{#strings.isEmpty(appeal)}"	//★使いやすい!appealが空でなければ表示
th:if="${a} == ${b}"	//stringの比較など

参考:https://qiita.com/55beagle/items/2208cea678dc948b715c

※th:if以外にこんな書き方ができる
https://www.thymeleaf.org/doc/tutorials/2.1/usingthymeleaf_ja.html#%E7%B9%B0%E3%82%8A%E8%BF%94%E3%81%97%E3%82%B9%E3%83%86%E3%83%BC%E3%82%BF%E3%82%B9%E3%81%AE%E4%BF%9D%E6%8C%81
6.2

th:text="${prod.inStock}? #{true} : #{false}"
演算
th:text="|${#aggregates.sum(productList.![price])}円(+税)|"	//productListのprice項目を合計する(springの記法)
https://qiita.com/55beagle/items/2208cea678dc948b715c

【formの処理】

■th:action
formの送信先を指定するaction属性は、静的に指定しても良いのだが、Springbootでは送信先のURLは可変になることが多いため、基本的には以下のように指定する。

■作成(create)と編集(edit)を同じformで管理したい場合のベストプラクティス

以下のように指定する

<form th:action="@{''}" method="post" th:object="${client}">
  <input type="hidden" th:field="*{id}">	//※1
  …

th:action="@{''}"(自分自身)を指定することで@GetMappingされてきた値でそのままPost送信することができる。(検討th:action=”@{|path|}”)
すなわち、現在のURLがhttps://appName/client/1/createであるとき、th:action=”@{‘’}”でpostすると、action=”/client/{clientId}/create”と読み替えられるため、以下のPostMappingでキャッチできる。

@PostMapping("/client/{clientId}/create")
public String createClient() {
  return "client/form";
}

ただし、※1に記したth:field="*{id}"を渡してあげる必要があるので注意すること

・同じページから相対パスでリクエストを出しわけたい場合
1つのページでcreate、edit、削除を管理したいことがある。その場合は以下のように指定すれば相対パスとなる。

th:action="@{form}"

現在のURLがhttps://appName/client/1/planであるとき、上記はaction=”/client/{clientId}/form"に読み替えられる。※現在のURLの最後のplanがformに置き換わっている点に注意

ただし、このときリクエスト自体は送られるが、遷移するURLが変わってしまう。
例えば、エラー等でページをreturnすると以下のような遷移となり、同じページであるにもかかわらずURLが変わるため、その後のリクエストが正常に送られなくなってしまう。

//元パス
http://page/form.html
//処理成功※redirect
http://page/form.html
//エラー時※return
http://page/form/create.html

・パラメータを送るなどして対応することもできるが、JavaではSPAに対応しておらず、推奨できないと思われる。
作成ページと、編集削除ページは分ける。
※削除は基本的にredirectなので、作成ページまたは編集ページと一緒に実装してもOK

■チェックボックスの表示 ※詳しくはSpringDBマニュアルで
formに出すのは簡単だが、ビューに出力するのみの場合方法がない。
disabled属性を指定する方法もあるが、
th:ifでチェックボックスのture、falseによってメッセージなどを出しわける方法がよさそう。

<div th:object="${client}">
<div th:each="a : ${areaList}"> //選択肢のリストを呼び出し出力
  <input type="checkbox"
  th:text="${a}" //選択肢がテキストで表示される
  name="areaName" //modelのareaNameフィールドに値を登録する
  th:value="${a}" //選択肢をそのままvalueとして設定
			//valueは配列としてpostされる
                  //valueを選択肢の名前と別にしたい場合はListではなくMapにする
  th:checked="${#lists.contains(areaNames, a)}" //表示の際、すでに選択されているものにはチェックを付ける ※後述
  >
</div>
</div>

・th:checked="${#lists.contains(areaNames, a)}"について
リストareaNamesにaが含まれる場合Trueを返す

■ドロップダウンリスト(プルダウン) :select, option

<select name="sampleList">
	<option
	 th:each="a : ${sampleList}"
	 th:value="${a}"
	 th:text="${a}"
	 th:selected="${sampleList} == 'defaultValue'"
	 ></option>
</select>

・デフォルト値の指定 optionを手動で1行追加するだけ

<select name="sampleList">
	<option value="選択してください">選択してください</option>	//デフォルト値を追加
	<option
	 th:each="a : ${sampleList}"
	 th:value="${a}"
	 th:text="${a}"
	 th:selected="${sampleList} == 'defaultValue'"
	 ></option>
</select>

■input type=”time” th:field=”*{time}”
この値はStringとして送られる。modelでデータをTimeなどに指定しているとエラーになる。
これは、Stringをオブジェクトのプロパティとして登録する際にmodelがTime型になっているという型の不一致の扱いとなるため。
type=”text”とすれば良いのだが、type=”time”でルーレットのUIが利用できるので使用したい。
コンバーターを自力で設置するか、アプリケーション内でTime型に書き換える必要がある。
アプリケーション内で書き換える場合は、HTML側ではth:fieldではなくnameで値を取得し、アプリケーション内でキャストする。
どちらも型変換までしてTime型のメリットを享受できるかというと疑問。
modelもString型で処理をしてしまった方が無難。もし、登録した時間を集計したり、Time型のメソッドを使用する場合には型の変換を検討する。

値の受け取り
@PostMapping("/form")
public String example(@RequestParam String genreName) {	//@RequestParamで受け取るが、引数名とパラメータ属性名が同じなら省略可?
}

※ただし、@PathVariable("id")を使用する場合、@ModelAttributeが効かないので、引数にModel.modelを追加、
 model.addAttributeして明示的にMVCモデルにセットする必要がある。

・実装

<form action="/form" method="post" th:object="${obj}">	//model名を指定する
	<input type="text" th:field="*{●属性名●}">
//modelのどのフィールドに登録するか指定する name属性は指定しなくてOK
	//th:field="${obj.●属性名●}"と同義
		//name="●属性名●" th:value="${formClass.●属性名●}"とも同義
</form>

この場合、出力すると以下に置き換わる

<input type="text" id=●属性名● name="●属性名●" value="">
エラー処理 例外処理

@Validでエラーをキャッチしたもの(@Dataのエラー)をthymelefで出力する

エラーメッセージは出力する箇所を

などで作成
メッセージの内容は@Dataの該当オブジェクトで指定する
例)@NotBlank(message="値を入力してください")

・実装 ※th:object="${obj}"でオブジェクトを指定しているブロックの配下であること

<div>
  <span th:if="${#fields.hasErrors('name')}" th:errors="*{name}"></span>
  //"obj"の'name'フィールドで設定しているエラー(@NotBlankなど)が出力される。
 //メッセージを指定している場合はそのメッセージが出力される。
</div>

※springbootのクラスは以下

class="form-text text-muted"

【レイアウト】

テンプレートを階層(レイヤー)化、または部品(フラグメント)化し、共通部分をまとめることで保守性を高めることができる。
※親テンプレートでオブジェクトを使用している場合に、子テンプレートでどのオブジェクトを参照しているのか見えなくなるため、あまり複雑な構造にしすぎないこと

■参考:
https://macchinetta.github.io/server-guideline-thymeleaf/current/ja/ArchitectureInDetail/WebApplicationDetail/TemplateLayout.html
~部品(フラグメント)化する場合~
■部品となる子テンプレートでは、部品にしたい箇所をth:fragmentで括る。

<html
	xmlns:th="http://www.thymeleaf.org/"
>
~~~
<div th:fragment="top-image" th:remove="tag">
	//部品
</div>

※必要なのは部品のみでそれをくくっているdivは必要ないケースがほとんど。
『th:remove="tag"』を付与することで外枠のdivタグは反映されない。

■親テンプレートではth:replaceで呼び出す。

<div th:replace="~{client/fragment/top-image :: top-image}"></div>

※”~{●親テンプレートのパス● :: ●fragment名●}”
親テンプレートのパスとは、templateフォルダ以下の絶対パス
~階層(レイヤー)化する場合~
fragmentのみだと親テンプレートでth:objectを使っていた場合に子テンプレートでもth:objectを呼び出さないといけない。
insertとかreplaceを使用してできるかな…。

レイアウトを使用する場合は親テンプレートにも子テンプレートにも以下を記述し、thymeleafのテンプレート機能を使うことを明示する。

<html
	xmlns:th="http://www.thymeleaf.org/"
	xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
>

子テンプレートでは、親テンプレートをのパスを指定する。
例えば親テンプレートのパスがlayoutフォルダ直下の『common.html』である場合は以下のようになる。
※波カッコの前に~が入っていることに注意

<html
	xmlns:th="http://www.thymeleaf.org/"
	xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
	layout:decorate="~{layout/common}"	//追記
>

親テンプレートでは、子テンプレートを埋め込みたい位置に以下のように記述する。

<div layout:fragment="contents"></div>

子テンプレートでも同様の値を使用し、親要素に反映させたい内容を記述する。

<div layout:fragment="contents">
	//親テンプレートに反映させたい内容を記述
</div>

Thymeleaf Layout Dialectモジュールによる共通画面作成............................................................
■導入
Spring Bootのスターターには入っていないため設定を行う
・手順
pom.xmlを開く
依存関係タブ>追加
グループId: nz.net.ultraq.thymeleaf
アーティファクトId: thymeleaf-layout-dialect
pom.xmlを保存

※アプリケーションを実行中の場合は
 設定を反映させるために再実行すること

■共通ページの用意
templateフォルダ直下にlayoutフォルダを追加
layoutフォルダ内にcommon.htmlを作成 (これが共通レイアウトになる)

※index.htmlにメタデータの記述をしてしまわないように注意
あくまでcommon.htmlで設定をするため、BootStrapなどのメタデータはこちらに記述する。
index.htmlも、このcommon.htmlを継承して画面を表示する。

・htmlタグにネームスペースを追記
・必要に応じメタ情報でスタイルシートを適用
 ※BootStrapの設定などはここで記述する
・個別コンテンツを埋め込むカ所を指定する

<html
	xmlns:th="http://www.thymeleaf.org/"
	xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
>
<head>
<meta charset="UTF-8">
<!-- 必要に応じメタ情報を追記 -->
<meta name="viewport" content="width=device-width, initianl-scale=1, shrink-to-fit=no">
<link rel="stylesheet" href="/css/bootstrap.min.css">
<title>共通レイアウト</title>
</head>

<body>
<h1 class="m-3">スクール管理ボード</h1>
<nav class="my-3" style="background: azure;">●ナビゲーションバー●</nav>

<!-- 個別コンテンツを埋め込む場所を指定 -->
<div class="container" layout:fragment="contents"></div>

</body>
</html>

■個別ページに共通レイアウトを呼び出す
例)index.htmlに反映させる場合
※共通レイアウトで指定したcssも反映される

<!-- ネームスペースを追加 &使用する共通レイアウトを指定 -->
<html
	xmlns:th="http://www.thymeleaf.org/"
	xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
	layout:decorate="~{layout/common}"
>
<head>
<meta charset="UTF-8">
<title>スクール管理ボード</title>
</head>

<body>

<!-- 個別コンテンツここから -->
<div layout:fragment="contents">

●コンテンツ●

</div>
<!-- 個別コンテンツここまで -->

</body>
</html>

【appendix】

■th:hrefでidのパラメータ(URL)がおかしくなる(文字化け?)
>『|』で囲むこと。上記の文字連結を参照

<a th:href="@{|/client/*{id}/edit|}">

■チェックボックスの値をnullで送信するとエラーになる
MissingServletRequestParameterException
>@RequestParamで値を送信していたが、このアノテーションは必須がデフォルトになっている。
 以下を指定

@RequestParam(required = false)

■Thymeleafのファイルはどこ?
>Maven 依存関係

■#について
https://teratail.com/questions/4676

#{...} :
読み込んであるプロパティでprop.nameのようにアクセスすることが可能

■条件に応じて読み取り専用にする
>|??|
th:readonly="${if条件}"
th:disabled="${if条件}"
|

例)

th:disabled="*{username != null && username != ''}"	//空または何か値があると入力不可(nullのみ許容)

※hasErrors()でreturnすると値がnullから空文字に変わってしまうため上記の条件式を書くことがあった。

【エラー対応】

■layout: fragmentで部品化したコンテンツで、個別にjsファイルを読み込みたいが読み込まない。
>bodyの直前で読み込むのが一般的だが、部品化されるのはlayout: fragmentのdiv内要素のみ。そのためdiv内でjsファイルを読み込む必要がある

<body>
<div layout:fragment="contents" th:object="${client}">
~コンテンツ~
<script type="text/javascript" src="/js/zipsearch.js"></script>
	//読み込まれる
</div>
<script type="text/javascript" src="/js/zipsearch.js"></script>	//読み込まれない
</body>

■th:action=@”{‘’}”が動作しないときのチェックポイント
まず、Controllerがpostをキャッチしているか確認
・していない場合
form送信後のURLを確認し、MappingしているURLと一致するかを調査する
・している場合
returnで返しているURLが合っているか確認する。
良くある間違い return “redirect:form” //相対パスになっている。”redirect:/form”とする

html2canvasでHTMLを画像化する備忘録

今回、スケジュールをオブジェクトで動的に表現しているHTMLのDOMを画像化する要件が発生した。

これを画像化するためにhtml2canvasを使用した。

導入

公式サイトからhtml2canvasの最新版をダウンロードして、使用する。
今回は現状の最新版であるv1.4.1の以下のファイルを選択した。
html2canvas.min.js

このjsを読み込んであげるのみ。
この使いやすさもhtml2canvasの魅力の1つ。

使い方

公式ドキュメントを読めばわかるが、使い方はいたってシンプル。
~HTML~

<div id="target">サンプル<div>

~js~

html2canvas(document.querySelector("#target"), options).then(canvas => {
    document.body.appendChild(canvas)
});

これでtargetのDOMが画像としてbody末尾に挿入される。
optionsにオプションを指定して条件を変えることができる。
optionsの一覧はOptions - html2canvasで確認できる。

例)

html2canvas(document.querySelector("#target"), {
        windowWidth: 1200,
        width: 1200
    }).then(canvas => {
    document.body.appendChild(canvas)
});

画像ダウンロード処理

画像ダウンロードはaタグにダウンロード用のリンクを埋め込むことで実現できる。
しかし、ダウンロードしたい要素は実際に描画されるわけではないのでhrefに設定するパスを静的に取得できない。
html2canvasによって作成されるキャプチャはcanvas要素で、そのURLはcanvas.toDataURL()で取得することができるため、これを利用する。
どこでも良いが、ボタンの後などに隠しリンク(aタグ)を設置し、これにURLを設定してクリックさせる。
~HTML~

<div id="outputBtn" class="btn btn-primary m-3">
  画像表示
</div>
<a id="getImage" href="" style="display: none"></a>

~js~

const outputBtn = document.getElementById("outputBtn");  //ボタン
const element = document.getElementById("schedule-outline");  //画像化したい要素
const getImage = document.getElementById("getImage");  //ダウンロード用隠しリンク

outputBtn.addEventListenner('click', function() {
  html2canvas(element).then(canvas => {
    getImage.setAttribute("href", canvas.toDataURL());
    getImage.setAttribute("download", "sample.png");
    getImage.click();
  });			
})

背景色を透過する

Optinosに以下を指定する

backgroundColor: null;

エラー対応

html2canvas(...).then is not a function

thenは関数じゃないよと言われている。html2cavasのメソッドにthenは存在しないようだ。
調べたところ、html2canvasのバージョンが最新ではないよう。
このとき、古いドキュメントを参照していて、最新バージョンではないhtml2canvasを使用していた。
最新版を入れなおすと解消した。

Widthが効かない

これもhtml2canvasが最新バージョンではなかったことが原因のよう。最新バージョンを入れなおして解消した。

Invalid shorthand property initializer

訳すとプロパティ初期化の省略形違反エラー。
Optionsのプロパティの指定の仕方が間違っている。
【誤】

Width = 100

【正】

Width: 100
画像が見切れる

html2canvasは、デフォルトでは実際にレンダリング(描画)されている範囲しかキャプチャ対象としていない。そのため、ブラウザを小さい幅で表示していればそれに合わたサイズになってしまう。
これを、最大サイズで取得、表示したい場合にはOptionsに以下を設定する。

windowWidth: 1200,    //取得する要素のサイズを指定する
width: 1200    //キャプチャを表示する際に、どのくらいのサイズで表示するかを指定する
画像がぼやける

Optinosでscaleを設定することで解像度を上げることができる

scale: 2

わかりにくければ、一度scale: 0.5とかに設定するとさらにぼやけるので、しっかり解像度が変更されていることがわかる。

MetaMaskでイーサリアムの購入ができない?コインチェックからの送金で解決

今回坂本龍一のnftをadam by GMOで購入した。
adam by GMOでのnftの購入はイーサリアムでのみ可能となっているため同仮想通過を購入したのだが、引っかかるポイントが非常に多かったのでメモ。

坂本龍一のnftについて

ニュースでも話題になった『メリークリスマス ミスターローレンス』の2021年7月30日演奏音源。
右手の演奏分の1音1音をnft化したものである。
2021/12/21から初回販売が開始され、初販分は各音1万円となっている。
僕が購入したのはこれが再販されていたもので、1/23に158,000円で落札した。
僕自身は坂本龍一の熱狂的なファンというわけではないが、もともとピアノ音が好きであることと、坂本龍一が世界的なアーティストであること。
また、僕自身好きな曲であること。
そして、nftを管理している会社が信頼のおける会社であること、が購入の決め手となった。
ちなみにnftの管理会社はadam by GMOで、僕はDMMという会社が好きで、DMMと思って購入したがあとからよく確認したらGMOだった。
おそらく寝ぼけていたのだと思う…
GMOはお名前.comで利用したことがあるが、あとからWhois情報に課金を迫られたりあまり好きではなかった。
さておき、adam by GMOの利用は、入札は自由にできるのだが、落札後の入金は仮想通過(イーサリアム)でしか行うことができず、さらに今回はオークションのため3日以内に入金を済ませなくてはならないという厳しい条件が付いてきた。
このイーサリアムの購入に苦戦したのでその流れを備忘録として残しておく。
ちなみに当たり前のことだが、仮想通過の取引など、寝ぼけた状態で絶対に行ってはいけない。

adam by GMOでの取引はMetaMaskで行う

お話してきた通りadam by GMOではイーサリアムでの取引しか行うことができない(2022/1月時点)のだが、決済に進もうとすると、MetaMaskというアプリケーションの利用が要求される。
このMetaMaskはスマホアプリと、PCブラウザではGoogleChromeのアドインでも導入することができる。
このMetaMaskを使用してイーサリアムの取引をする。

MetaMaskでイーサリアムの購入ができない

このMetaMask、なかなか曲者で、ここでイーサリアムを購入しようとすると、wyreまたはTRANSAKという、法定通貨と仮想通過を換金する会社を通すことになる。
この2社で決済を試してみたが、僕は結局この2社で決済ができず、別の方法を取ることになる。
まず、購入の過程で、説明はすべて英語である。さらにサポートに問い合わせようにも、こちらも英語しか対応していない。
という前提で、wyreとTRANSAKを利用した所管。

wyreでの決済

wyreではApplePayでデビットカードを推奨している。
しかしそもそもApplePayに登録できるデビットカードというのは非常に種類が少ない。
僕の手持ちのカードはご多分に漏れず対応していなかったのでこちらは断念。

TRNSAKでの決済

次にTRANSAK、こちらはクレジット決済が利用できるということで試してみた。
しかし、何度カード情報を入力してもエラーになる。JCBだからか?もしかするとVISAならいけるのかもしれないが、VISAのカードは持っていないのでこちらも断念。
日本語のサイトであればここでサポートに問い合わせるところだが、決済という専門分野でとても英語でやり取りする気にはなれず、MetaMaskでのイーサリアムの購入は断念した。

コインチェックでイーサリアムを購入し、MetaMaskに送金する

MetaMaskのみでの決済で挫折したため、他の販売所で購入してMetaMaskに送金した。
最終的にコインチェックを採用したが、BitFlyer、DMM Bitcoinも少し検討した。
BitFlyerは入力フォームの予測変換やちょいちょい挙動がスムーズでないところがあった。
また、送金メニューを見ると、『ビットコインを送金』と表示があり、イーサリアムはそもそも送金できないのか?と思ったのでやめた。
DMM Bitcoinは、そもそも送金の仕組みがわからなかった。
仮想通過を購入せずに口座開設だけした形なので、もしかすると仮想通過を入手したタイミングで送金の可否が表示されるのかもしれない。
どれを採用するにしても、まず利用のための本人確認が必要で待ち時間が発生するので、試すのであれば早めに登録→仮想通過の口座開設までしておくことをお勧めする。
ちなみにどのサービスでも口座開設までは無料で出来る。

コインチェックから即時送金

さて、今回はオークションでの落札ということで、3日以内にMetaMaskにイーサを送金する必要があった。
そのために以下の対応をしなくてはならない。

  1. コインチェックで日本円を入金
  2. 入金した円でイーサリアムを購入
  3. イーサリアムをMetaMaskに即時送金

1の入金についてコインチェックでは主に以下の入金方法から選択する

  • 銀行入金
  • コンビニ入金
  • クイック入金

しかし、コンビニ入金・クイック入金はなんと入金しても7日間はその使用が凍結され、送金はもちろん取引に使用することができない。
マネーロンダリングの観点らしいが、これではあんまりである。
即時送金をするためには銀行入金を行う。
指定の銀行口座に振り込むとそれがコインチェックに反映されるというもの。
あまりこの手の決済は利用したことが無く本当に即時反映されるのか?
された。
反映までおそらく1時間もかからなかっただろう。
銀行口座というアナログなものに振り込んで本当にコインチェックというアプリケーションに反映されるのか不安だったが、何の問題もなかった。だいぶリテラシーのおくれを取っていたようだ…
送金は、まずMetaMask側でパブリックアドレスをコピー。
その後コインチェック側で送金手配をし、宛先に先ほどのアドレスを指定すればいい。
ここは全く苦戦することもなく、時間もかからなかった。

まとめ

苦戦はしたが、なんとか入金を済ませ、nftを落札することができた。
発狂ポイントは英語サポートしかないMetaMaskでの入金というところだ。
このあたり、GMOに再び少し冷たい視線を注いでしまう自分がいるが、nftを購入したので一蓮托生である。
無礼な客ですが、今後もお付き合いのほど、よろしくお願いいたします。

Stripe、Paypal、サブスクリプション自動引き落とし特徴比較

サブスクリプションの導入にあたり、決済システムを検討した。
それぞれのサービスの比較は以下の記事でよく解説してあった。
Shopifyでまず導入したい決済「Stripe」と「PayPal」を比較 – コマースメディア株式会社

今回の記事では、サブスクリプションのポイントとして筆者がわからなかった、顧客がどのようにして支払いを完了するのか?について備忘録として書き留めておく。

今回のケース

Stripe

【概要】
Github、Slackなどでも採用されている決済システム

【特徴】

  • UIがおしゃれ
  • 支払いに当たり顧客側でのStripeアカウントの作成は不要
  • 決済にあたり住所や電話番号の入力も不要

【課題】

  • JCBの登録は事前に申請が必要
  • 銀行振込不可(あることはあるが、日本で対応していない?)
  • インボイス(請求書)が送られるタイミングや、本当に送られたのか?を確認する方法がわかりづらい

登録や課題については
Stripeサブスクリプション決済導入備忘録 - プログラミンGOO
でまとめたので参考に。
※例のごとく箇条書きで非常にわかりづらいので恐縮であります

Paypal

【概要】
古参大手フィンテック知名度が非常に高い。

【特徴】

  • 銀行振込可能(アカウント登録必要)
  • クレジットカード決済であればアカウント登録不要
  • アカウントを登録してくれれば、相手のPaypalアカウントの設定に基づいて行うことができるため、サブスクリプションの決済情報も相手のアカウント内で設定することができる。
  • クレジットカードをOCRでカメラ自動読み取りできる(有効期限の年月が逆に読み込まれるエラーあり)

【課題】

  • ボタンデザインがしょぼい。Smartシリーズが出たが全然使い物にならない
  • エラーがとても多い。戻るボタンでいろいろ挙動がおかしくなる。
  • 決済に当たって、住所、電話番号まで入力しなくてはならない

PayPalの登録や課題については
PayPalサブスクリプション登録備忘録 - プログラミンGOO
でまとめたので参考に。
※こちらも恐縮であります

まとめ

サブスクにおいて、PayPal決済は、UI、UX的にStripeに大幅な遅れをとっているといった感触でした。
とにかく、請求、顧客、商品の管理がわかりづらいしやりづらい。
PayPalの優っている点としては銀行振り込みに対応しているところと、日本語化が容易なところ、そして圧倒的な知名度
ただ、決済時の入力情報の多さという大きすぎるボトルネックのため、Stripeを採用する機会の方が圧倒的に多そうです。