技術情報 – こみなのメモ帳 / 趣味と実益のネタ帳 Sun, 28 Dec 2025 09:52:34 +0000 ja hourly 1 https://wordpress.org/?v=6.1.1 [play1]evolutions機能 /archives/1820/ Tue, 16 Dec 2025 16:04:28 +0000 /?p=1820 概要

playframework1に当初から内包されているDBスキーマを管理する機能の名前。普段、DB周りはJPAに任せてしまうことが多いのですが、先日初めて evolutionを使ってみたのでそのとき得た知見をまとめておこうと思います。

何ができるのか

JPA(Hibernate)は自動でデータベースのスキーマ更新を行うことができます。小規模開発ならJPAの自動更新(インデックスの削除など不可なケースもある)に任せてもいいのですが、ある程度のプロジェクトであればスキーマの変更を追跡、管理する必要が出てきます。

スキーマの変更をメンバー全員に周知したり、本番サーバのスキーマ変更を明示的に行ったり、いくつかの開発環境DBを同期させたり、などの場面で役立ちます。

準備

  1. Evolution機能を利用する際はまず、ディレクトリ db/evolutions を作成する。
  2. そのディレクトリの中に、1.sql, 2.sql, とDDLを作成する。
    スキーマ変更を行うたびに新しい番号でDDLを記述したファイルを配置する。

SQLスクリプトファイル

  • db/evolutions に配置するSQLスクリプトは、UpsとDownsの2つのパートから構成される。
  • Upsは変更するためのスクリプト。(例えば、 create table XXX...
  • Downsは元に戻すためのスクリプト。(例えば、drop table XXX...

マルチDB対応

マルチDBにも対応しています。マルチDBでは各DBに識別子を設定しますが、その識別子をスクリプトファイルの先頭に記述します。

1.sql, 2.sql, ... となるところが、DB-1.1.sql, DB-1.2.sql, ...,DB-2.1.sql, DB-2.2.sql,... といった形になります。

設定

application.conf による動作の制御方法

evolutions.enabled=false

Evolutions機能を利用しないときに false を設定する。

modules.evolutions.enabled=false

Evolutionsモジュールを利用しないときに設定する。

dbname.evolutions.enabled=false

マルチDBでDB指定してEvolutionsモジュールを利用しないときに設定する。

evolutions.autocommit=false

autocommitをしないときに指定する。

evolution.PLAY_EVOLUTIONS.textType=varchar(32768)

Evolutions機能で作成する管理テーブル play_evolutionsapply_script , revert_script , last_problem 列の型を指定する。未指定の時、oracleなら clob型、他は text型 となる。
デフォルトの hd2b を使う場合は varchar(32768) を指定する必要があった。

db/evolutions ディレクトリの存在でモジュールはONとなるので、上記設定で個別にOFFにしていく感じになります。

動作

DEVモード

DEVモードの時はリクエストが発生するたびにスキーマ状態の確認が割り込みます。そこで未適用のスクリプトが見つかった場合は適用を要求するページが表示されます。(例外オブジェクトの getMoreHTML にて適用するための <form/> を含む html を返すことで適用要求ページを実現している)

詳しくは調べていないのですが、UnexpectedException にラップされて <form /> が表示されない事象が発生したので以下のように views/errors/500.html を修正して対応しました。

適用要求ページが出ないで UnexpectedException が表示される
      <body>
        #{if play.mode.name() == 'DEV'}
          #{if exception?.cause instanceof play.exceptions.PlayException }
            #{500 exception.cause /}
          #{/if}#{else}
            #{500 exception /}
          #{/else}
        #{/if}
        #{else}
            <h1>Oops, an error occurred</h1>

db=mem で中身が空の場合は問い合わせなく勝手にスクリプトが適用される。

PRODモード

本番モードなら最初の起動時にスキーマ状態の検査が行われます。未適用のスクリプトが見つかった場合はエラーとなって起動中断となります。

アプリケーションが要求するDBスキーマとなっていないと起動しないようになっています。

コマンドライン

コマンドラインよりスクリプトの適用を制御します。

$ play evolutions

未適用のスクリプトを表示する。

$ play evolutions:apply

未適用のスクリプトを適用する。

$ play evolutions:markApplied

未適用と認識されているスクリプトを Evolutions機能を経由しないで適用したときに使用する。(未適用スクリプトを強制的に適用済ステータスにする)

$ play evolutions:resolve

スクリプトの実行中にエラーが発生すると問題が発生したスクリプトとして記録され、以降の適用が保留となる。そのときこのコマンドで問題が解決済とする。(問題発生ステータスをクリアする)

play evolutionsplay ev と省略することが可能です。

Tips

DDL作成

自分で一からSQLスクリプトファイルを書くのは大変なのでJPA(Hibernate)を利用すると楽ができます。

jpa.ddl=update
jpa.ddl=create

この設定にしておけば起動時にDDL変更を自動で行ってくれる。

jpa.debugSQL=true
hibernate.show_sql=true

DDL変更のSQLがログに出力されるのでこれをスクリプトファイルへ転記すればよい。

戻しについてはJPAからの出力は期待できないので自分でスクリプトを用意する必要があります。

module_key カラムについて

管理テーブル play_evolutions には、module_key というカラムが有、application.name の値が格納されていて、抽出条件にもなっています。

複数の playframeworkアプリが同一DBに対して Evolutionsを利用することを想定しているようです。

なので、application.name の扱いには注意しましょう。途中で変更したり、開発時だけ名前を変えていたりすると、予期しない挙動を招く可能性があります。

途中からEvolutionを利用するとき

既に開発が進んでいるプロジェクトで Evolutionsを利用するときの手順を示します。

  1. アプリケーション停止する。
  2. db/evolutions ディレクトリを作成する。
  3. 1.sql」 を設置(現時点のDBのDDLを取得して作成)
  4. play ev」コマンドを実行する。(管理テーブルが作成されて、1.sql が未適用として表示される)
  5. play ev:markedApplied」コマンドを実行して適用済とマークする。
  6. アプリケーション起動する。

以上になります。以降のDDL変更は、#.sql へ記述します。

h2dbで現時点のスキーマを出力する方法

sql> script nodata to 'C:\work\ddl.sql

データやスキーマをSQLとして書き出すことができるコマンド。今回はデータなしなので nodata を指定。

参考)https://h2database.com/html/commands.html#script

本番リリースするとき

新しいバージョンのアプリケーションに入れ替えるときは、停止中にDDL適用を行います。

  1. play ev:apply」コマンドを実行し、未適用のスクリプトを適用する。
  2. apply中にエラーが出てしまったらステータスがエラーになってしまうので、「play ev:resolve」でエラー状態を解消する。
    問題を解決して再びスクリプト適用を行う。

evolution.PLAY_EVOLUTIONS.textTypeの必要性

h2dbでは下図のような管理テーブル play_evolutions が作られます。

デフォルト設定の場合

ここで apply_scriptrevert_script 列のデータ型が clob型となっている点に注目してください。Evolutionsではこれらを resultSetからgetString(n) で取得しているのですが、Clob型のtoStringではテキストの中身ではなく `java.sql.Clob@123′ のようなオブジェクトID文字列になってしまいます。

そのオブジェクトID文字列とスクリプトファイルの中身を比較することになるために常にNGとなってしまうのです。

そこでDBデータ型を明示的に指定することでこの問題を回避することができるのです。

evolution.PLAY_EVOLUTIONS.textType=varchar(32768)

※ この設定だけ evolutionが単数形なので注意。

org.h2.jdbc.JdbcSQLSyntaxErrorException: 列名 “MODULE_KEY” が重複しています

想像ですが。昔の Evolutionsでは module_key による管理がなく、ある時から管理できるようになった。そのため昔に作られた管理テーブル play_evolutions に自動でカラムを追加してくれるマイグレーション機能が存在するみたいです。

で、module_keyが存在しているのにこのマイグレーション機能が誤作動してしまうと題名のようなエラーが発生することがあります。

具体的例としては、h2dbの DATABASE_TO_UPPERtrueになっていると大文字でテーブルやカラムが作成されてしまって存在チェックで誤検知が発生してしまうことになります。

play-1.5.3 + h2db@1.4.196 では小文字で play_evolutions が作成
play-1.7.2 + h2db@1.4.200 では大文字で作成

組み合わせでデフォルト動作が異なるので JDBC_URL で明示的に指定した方が良いです。

]]>
H2DBのLinked Tablesについて調べてみた /archives/1670/ /archives/1670/#respond Tue, 05 Aug 2025 14:40:20 +0000 https://www.komina.info/?p=1670 H2DBにはLinked Tablesという機能があります。外部テーブル(ほかのDB)へのテーブルリンクを作成して、あたかも H2DB 内に存在するテーブルかのように扱う機能です。

JDBCドライバで接続できるDBであれば、以下のようなコマンドで利用可能です。

CREATE LINKED TABLE LINK('org.h2.Driver', 'jdbc:h2:./test2', 'sa', 'sa', 'TEST');

社内に MySQL PostgreSQL などいろんなDBサーバが乱立しているようなときに使ったら便利そう、ということで調べてみることにしました。

環境構築

docker-composeを使ってお試し環境を作ってみました。h2db + mysql + postgresql な環境です。githubに一式を公開してあります。

https://github.com/komina77/h2db

git clone https://github.com/komina77/h2db.git
cd LinkedTables
docker-compose up -d

サンプルデータは MySQL の公式サイトにあるサンプルデータworld)を利用させてもらうことにします。これを mysqlpostgresql の両方の初期データとしてインストールすることにします。(postgresql へはそのままの文法では食わせることはできなかったので少し書き換えています)

city, country, countryLanguage の3つのテーブルから構成されるデータベースになります。

MySQLを参照してみる

実はH2DBのサーバに内蔵されているWeb管理画面はH2DB以外のDBにも接続できますので、これを利用してみたいと思います。

ホストOS上のブラウザで、http://localhost:8082/ を開き、保存済設定の中から「Generic MySQL」を選びます。ドライバクラスなどのデフォルト設定が表示されるので、JDBC URLやユーザ名、パスワード(mysql/mysql)を入力します。接続テストが通れば成功です。

PostgreSQLを参照してみる

同様に PostgreSQL のデータベースも参照することができます。

オートコンプリート機能も有効で、ちょっとしたSQLならすぐに試してみることができます。PostgreSQLの大文字小文字混在のカラムはダブルコーテーションで囲わないといけないので、逆に不便でした。

Linked Tables を定義してみる

mysqlpostgresql の準備ができたところで H2DB に接続して Linked Tables を作成してみたいと思います。

まずは mysqlworldデータベースの中の countryテーブルを my_country という名前で参照できるようにしてみます。

CREATE LINKED TABLE IF NOT EXISTS MY_COUNTRY(
'com.mysql.jdbc.Driver', 'jdbc:mysql://mysql-1:3306/world', 'mysql', 'mysql', 'country'
);

これだけで MY_COUNTRY というテーブルをローカルにあるテーブルのように参照できるようになります。

同様に、PG_CITYPG_COUNTRYLANGUAGE についてもテーブルを定義します。

CREATE LINKED TABLE IF NOT EXISTS PG_CITY(
'org.postgresql.Driver', 'jdbc:postgresql://postgres-1/world', 'postgres', 'postgres', 'city'
);

CREATE LINKED TABLE IF NOT EXISTS PG_COUNTRYLAUNGUAGE(
'org.postgresql.Driver', 'jdbc:postgresql://postgres-1/world', 'postgres', 'postgres', 'countrylanguage'
);

どんなSQLが発行されているか(MySQL

まずは条件なしで検索。

SELECT * FROM MY_COUNTRY 
SELECT * FROM country T

次は簡単な条件を付けてみます。

SELECT * FROM MY_COUNTRY where CODE ='JPN'
SELECT * FROM country T 
WHERE CODE>='JPN' AND CODE<='JPN'

単純な文字列の等号による一致条件のつもりだったのですが不等号の範囲検索に変換されてしまいました。なにか意図があるのでしょうがとりあえずヨシとします。

どんなSQLが発行されているか(PostgreSQL

SELECT * FROM PG_CITY 
SELECT * FROM public.city T
SELECT * FROM PG_CITY 
WHERE ID =10
SELECT * FROM public.city T 
WHERE ID>=$1 AND ID<=$2
ERROR:  column "id" does not exist at character 35

おっと。エラーが出てしまいました。どうやらDDLの段階でカラム名をダブルコーテーションで囲って大文字小文字を厳密に定義していたことが原因と思われる。。

DDLを修正してもう一度やり直し。PG_CITYPG_COUNTRYLANGUAGE についてもテーブル定義をやり直します。

SELECT * FROM PG_CITY 
WHERE ID =10
SELECT * FROM public.city T 
WHERE ID>=$1 AND ID<=$2
DETAIL:  parameters: $1 = '10', $2 = '10'

無事にクエリを発行することができました。

結合したらどうなるか(異DB同士

テーブル一つに対するクエリであれば、ほぼ等価の条件式が渡されるようなので、リンクされた側のDBで適切な実行計画が適用されることになりそうです。

では、2つのDBにまたがる結合をしたらどうなるのか。やってみたいと思います。

SELECT T1.NAME, T2.LANGUAGE  FROM MY_COUNTRY  T1
INNER JOIN PG_COUNTRYLAUNGUAGE  T2
ON T2.COUNTRYCODE = T1.CODE
AND  T2.ISOFFICIAL ='T'
WHERE T1.REGION  = 'North America'
;

北アメリカの国における公用語は?、という感じの意味合いになります。まず MY_COUNTRY を北アメリカで絞り込んだのち、国コードで PG_COUNTRYLANGUAGE から公用語を得る、という結合になることを想定してみました。

MySQL

SELECT * FROM country T WHERE CODE>='ABW' AND CODE<='ABW'
SELECT * FROM country T WHERE CODE>='AFG' AND CODE<='AFG'
SELECT * FROM country T WHERE CODE>='AFG' AND CODE<='AFG'
SELECT * FROM country T WHERE CODE>='AIA' AND CODE<='AIA'
SELECT * FROM country T WHERE CODE>='ALB' AND CODE<='ALB'
SELECT * FROM country T WHERE CODE>='AND' AND CODE<='AND'
SELECT * FROM country T WHERE CODE>='ANT' AND CODE<='ANT'
SELECT * FROM country T WHERE CODE>='ANT' AND CODE<='ANT'
SELECT * FROM country T WHERE CODE>='ARE' AND CODE<='ARE'
SELECT * FROM country T WHERE CODE>='ARG' AND CODE<='ARG'
SELECT * FROM country T WHERE CODE>='ARM' AND CODE<='ARM'
:
:

PostgreSQL

SELECT * FROM public.countrylanguage T WHERE ISOFFICIAL>=$1 AND ISOFFICIAL<=$2
-- parameters: $1 = 'T', $2 = 'T'

どうやら想定と逆に、PG_COUNTRYLANGUAGE から公用語の一覧を取得したのち、対応する国の一覧を取得。クエリには北アメリカでの絞り込みは入っていないので H2DB側で一番最後に行われたようです。

実行計画を見てみる

事後になりますが H2DBでの実行計画を見てみます。

EXPLAIN
SELECT T1.NAME, T2.LANGUAGE  FROM MY_COUNTRY  T1
INNER JOIN PG_COUNTRYLAUNGUAGE  T2
ON T2.COUNTRYCODE = T1.CODE
AND  T2.ISOFFICIAL ='T'
WHERE T1.REGION  = 'North America'
;
SELECT
    "T1"."NAME",
    "T2"."LANGUAGE"
FROM "PUBLIC"."PG_COUNTRYLAUNGUAGE" "T2"
    /* PUBLIC."": ISOFFICIAL = 'T' */
    /* WHERE T2.ISOFFICIAL = 'T'
    */
INNER JOIN "PUBLIC"."MY_COUNTRY" "T1"
    /* PUBLIC."": CODE = T2.COUNTRYCODE */
    ON 1=1
WHERE ("T1"."REGION" = 'North America')
    AND (("T2"."ISOFFICIAL" = 'T')
    AND ("T2"."COUNTRYCODE" = "T1"."CODE"))

FROM句で指定していたテーブルが MY_COUNTRY から PG_COUNTRYLANGUAGE に変わっていますね。H2DB では結合順序指定するヒント句はサポートされていません。性能を高めるには希望する結合順になるまで SQLをこねくり回すしかなさそうです。

結合したらどうなるか(同DB同士

同じDBにあるテーブル同士の結合も試してみます。

EXPLAIN
SELECT T1.COUNTRYCODE ,T1.DISTRICT , T1.NAME , T2.LANGUAGE
FROM PG_CITY T1
INNER JOIN PG_COUNTRYLAUNGUAGE T2
ON T2.COUNTRYCODE =T1.COUNTRYCODE 
WHERE T1.POPULATION >7000000
AND T2.ISOFFICIAL ='T'
;
SELECT
    "T1"."COUNTRYCODE",
    "T1"."DISTRICT",
    "T1"."NAME",
    "T2"."LANGUAGE"
FROM "PUBLIC"."PG_CITY" "T1"
    /* PUBLIC."": POPULATION > 7000000 */
    /* WHERE T1.POPULATION > 7000000
    */
INNER JOIN "PUBLIC"."PG_COUNTRYLAUNGUAGE" "T2"
    /* PUBLIC."": COUNTRYCODE = T1.COUNTRYCODE */
    ON 1=1
WHERE ("T2"."COUNTRYCODE" = "T1"."COUNTRYCODE")
    AND (("T1"."POPULATION" > 7000000)
    AND ("T2"."ISOFFICIAL" = 'T'))

人口が700万人超の都市のある国の公用語の一覧を得るクエリです。PostgreSQL へは以下のクエリが送信されました。

SELECT * FROM public.city T WHERE POPULATION>=$1
-- parameters: $1 = '7000000'
SELECT * FROM public.countrylanguage T WHERE COUNTRYCODE>=$1 AND COUNTRYCODE<=$2
-- parameters: $1 = 'BRA', $2 = 'BRA'
SELECT * FROM public.countrylanguage T WHERE COUNTRYCODE>=$1 AND COUNTRYCODE<=$2
-- parameters: $1 = 'GBR', $2 = 'GBR'
SELECT * FROM public.countrylanguage T WHERE COUNTRYCODE>=$1 AND COUNTRYCODE<=$2
-- parameters: $1 = 'IDN', $2 = 'IDN'
SELECT * FROM public.countrylanguage T WHERE COUNTRYCODE>=$1 AND COUNTRYCODE<=$2
-- parameters: $1 = 'IND', $2 = 'IND'
SELECT * FROM public.countrylanguage T WHERE COUNTRYCODE>=$1 AND COUNTRYCODE<=$2
-- parameters: $1 = 'IND', $2 = 'IND'
SELECT * FROM public.countrylanguage T WHERE COUNTRYCODE>=$1 AND COUNTRYCODE<=$2
-- parameters: $1 = 'JPN', $2 = 'JPN'
SELECT * FROM public.countrylanguage T WHERE COUNTRYCODE>=$1 AND COUNTRYCODE<=$2
-- parameters: $1 = 'CHN', $2 = 'CHN'
SELECT * FROM public.countrylanguage T WHERE COUNTRYCODE>=$1 AND COUNTRYCODE<=$2
-- parameters: $1 = 'CHN', $2 = 'CHN'
SELECT * FROM public.countrylanguage T WHERE COUNTRYCODE>=$1 AND COUNTRYCODE<=$2
-- parameters: $1 = 'KOR', $2 = 'KOR'
SELECT * FROM public.countrylanguage T WHERE COUNTRYCODE>=$1 AND COUNTRYCODE<=$2
-- parameters: $1 = 'MEX', $2 = 'MEX'
SELECT * FROM public.countrylanguage T WHERE COUNTRYCODE>=$1 AND COUNTRYCODE<=$2
-- parameters: $1 = 'PAK', $2 = 'PAK'
SELECT * FROM public.countrylanguage T WHERE COUNTRYCODE>=$1 AND COUNTRYCODE<=$2
-- parameters: $1 = 'TUR', $2 = 'TUR'
SELECT * FROM public.countrylanguage T WHERE COUNTRYCODE>=$1 AND COUNTRYCODE<=$2
-- parameters: $1 = 'RUS', $2 = 'RUS'
SELECT * FROM public.countrylanguage T WHERE COUNTRYCODE>=$1 AND COUNTRYCODE<=$2
-- parameters: $1 = 'USA', $2 = 'USA'

700万人超の都市の一覧を取得したのち、国コードを使って1国ずつ PG_COUNTRYLANGUAGE を取得していますね。ISOFFICIAL については記載がないので H2DB側で絞り込みを行っているのでしょう。

同じDBにあるテーブルであっても、結合をそのDBに任せず H2DB内で行っていることが分かりました。

まとめ

  • H2DB には他のDBのテーブルをH2DB内のテーブルのように見せかける機能がある。複数のシステムのDBを透過的に扱うのに便利。
  • 外部DBのテーブル定義に依存する。カラム名に大文字小文字混在を許すようなテーブルを参照するときは失敗することがあるので、事前に調査してビューなどを介すなど工夫が必要。
  • テーブル1つに対するクエリであればテーブルを持つDB上で抽出が行われるので、性能的には問題なさそう。
  • リンクテーブル同士での結合はサポートされている。しかし、H2DBの判断で実行計画が決まってしまうので性能が出ないことがある。
  • 結合するリンクテーブルが同じDBのテーブルであっても、結合がリンク先のDBに任されない。H2DBの判断によって行われる。

異なるシステムで使われているDBのテーブルを一時的に参照するような用途では便利かもしれない。しかし、複雑なクエリで問い合わせるときは性能を出すために苦労することになりそうだ。

]]>
/archives/1670/feed/ 0
OneThirdCMSを動かしてみたかった話 (2) /archives/1647/ /archives/1647/#respond Thu, 12 Jun 2025 16:06:58 +0000 https://www.komina.info/?p=1647 前回、PHP8.1では動かすことが叶わず、PHP7系が必要だったことが判明したところで終わりました。あのままフェードアウトする予定だったのですが、少しモチベーションが回復してDockerイメージのベースをちょいと変えるだけということに気づいたので、もうちょっとだけ頑張ってみることにしました。

というわけで Dockerfile の一行目を以下のように書き換えてビルドしなおしました。

FROM php:7.4-apache-bullseye

エラーが出ずにインストール完了しました。さっそくURLに /login を付けてログインしてみることにします。

ん?なんだかデザインが崩れているような。開発者ツールでコンソール出力を確認してみたところ、以下のようなエラーが。

Azureで生成される長いURLのせいで難しいことが書いてあるように見えますが、よくよく読むとスタイルシートの読み込みで https を使っていないことで怒られているようです。このせいでスタイルシートが読み込まれなかったようです。

デザイン崩れはしょうがないとして、ユーザIDとパスワードを入力してみたのですが、ログイン失敗になってしまいます。どうやらログインは ajaxで処理されていてそのエンドポイントのURLがこれまた https を使っていないことでエラーになっていました。

これはおそらく Azure App Service 側の問題のような気がしてきました。プログラム側からホストのURLを取得したときに https://… ではなく http://… と認識されてしまっているのかも?

いったんサービスを停止し、/backup/data 配下を確認してみることにします。(うまくスクリプトが動いていればコンテナ停止時にコンテナ内の /var/www/html 配下のファイルが /backup/data へ同期されて見えるようにっているはずです)

そこで config.php というファイルが作成されているのを見つけ開いてみると案の定、http:// で設定されていました。オンラインインストール時にこのURLで登録されたのでしょうか。

	// path
	$config['site_path'] = '/var/www/html';
	$config['site_url'] = "http://onethirdcms-g9gtarhtafc3dte9.japaneast-01.azurewebsites.net/";
	$config['site_ssl'] = "http://onethirdcms-g9gtarhtafc3dte9.japaneast-01.azurewebsites.net/";
	$config['files_path'] = '/var/www/html/files';
	$config['files_url'] = "http://onethirdcms-g9gtarhtafc3dte9.japaneast-01.azurewebsites.net/files/";

config.php の当該箇所を修正して再アップロード。サービスを起動しなおします。

開発ツールを起動しながらページを開くと、今度は Mixed Contentエラーは出なくなりました。スタイルシートのリンクも https://… に切り替わっています。:-)

これでイケるのかも!と思って /login へアクセスしてみたのですが何とエラーも何も出ずホームページのまま。ログイン画面へ遷移しなくなってしまいました。開発者ツールにはエラーは出ない。ログイン画面が表示されない。状況は悪化してしまいました。

これ以上は内部の仕組みを調べながらでないと難しそう。今度こそあきらめることにします。ここまで読んでいただきありがとうございました。ということで OneThirdCMSを動かしたい方は実績のあるレンタルサーバを使いましょう。さよなら。

追記

ローカル環境ではログインしてCMSとして使えそうなので、ローカル環境で作成した資材を Azure App Service の公開フォルダへアップロード(config.phpを除いて)するという手が使えるかもしれません。ちょっと手間ですがローカルPC側の backup.sh で工夫すれば自動で同期をとるといった方法も使えるかもしれませんね。

追記2

少し稼働してみて気づいたこと。コンテナ起動時のスクリプトで停止シグナルはトラップするようにしており、手動でApp Serviceを停止したり再起動するときはトラップできているようですが、アクセスが途絶えて自動でシャットダウンするときには動いていないようです。

どうやら App Service on Linux はそうゆうものらしく、カスタムコンテナ以外ならスタートアップスクリプトに仕込むことで対処できるような話が見つかりました。

今回はカスタムコンテナなので初っ端に docker run が動いてしまうので対処のしようが無さそうです。

Copilotに相談したらに Azure Container Apps を使えば?みたいな回答をいただきました。

]]>
/archives/1647/feed/ 0
OneThirdCMSを動かしてみたかった話 (1) /archives/1610/ /archives/1610/#respond Mon, 09 Jun 2025 08:54:20 +0000 https://www.komina.info/?p=1610 このサイトはWordPressをAzureのAppServiceのフリープラン+CDNで細々と運用していまが、あまりお金をかけてないためWordPressが不安定なところが否めず。何かよい引っ越し先はないかと「軽量で無料なCMSを探しています」的な質問を Copilot に尋ねてみたわけです。

すると福岡発の純国産CMSであるOneThirdCMS(公式ページ https://onethird.net/)を推してくるではありませんか。SQLiteもサポートしていてDBサーバを必須としていないところもポイント高い。というわけで、さっそく試してみることにしました。

結論から言うとうまく行かなかったのですが、紆余曲折あり長くなったので記録もかねてここに記しておきます。

Dockerコンテナで動かす

ダウンロード資材は2種類あって、1つはWindowsPCでWebサーバを起動してその上でOneThirdCMSを動かすもの。もう1つはWebサーバ上で動くオンラインインストーラ。

私のゴールは Azure AppService(F1プラン)で単一コンテナで動かすことなので後者を使うことにします。Docker 公式の phpapacheコンテナを動かせばすぐに使えるかと思ったのですが・・・

  • mod_rewrite が必須(=Apacheで動かすことが前提)。これがクリア。
  • SQLiteで稼働する予定でも pdo_mysql が有効になっている必要あり。
  • オンラインインストールでzip展開するのでzipが有効になっている必要あり。
  • PHP8.2 から導入された仕様「動的プロパティの作成が非推奨」により "Deprecated: Creation of dynamic property Ut::$circle is deprecated in /var/www/html/module/utility.php on line 296 OneThird CMS" というようなエラーが発生するので PHP8.1 で動かす。

ということで php:8.1-apache-bookworm をベースに Dockerfile を作成しました。

FROM php:8.1-apache-bookworm

# Install zip & mysql
# Install required packages
RUN apt-get update && apt-get install -y libzip-dev locales

# Install PHP extensions
RUN docker-php-ext-install zip pdo_mysql

# Set up Japanese locale
RUN sed -i '/^# *ja_JP.UTF-8 UTF-8/s/^# *//g' /etc/locale.gen && \
    locale-gen && \
    update-locale LANG=ja_JP.UTF-8

ENV LC_ALL=ja_JP.UTF-8

# Set timezone to Asia/Tokyo
RUN apt-get install -y tzdata && \
    ln -sf /usr/share/zoneinfo/Asia/Tokyo /etc/localtime && \
    echo "Asia/Tokyo" > /etc/timezone

# Set cron 
RUN apt-get install -y cron

# Set rsync
RUN apt-get install -y rsync

# enable rewrite
RUN a2enmod rewrite

# custom php entrypoint
COPY custom-php-entrypoint "/usr/local/bin/"
RUN chmod +x "/usr/local/bin/custom-php-entrypoint"
ENTRYPOINT [ "/usr/local/bin/custom-php-entrypoint" ]
CMD ["apache2-foreground"]

WORKDIR /var/www/html

Docker勉強中なのでイケてないところがあるかもしれません。

githubのプライベートレポジトリにまとめていますが、GitHub Actions の勉強もかねて Docker Hubへ公開しています(https://hub.docker.com/r/komina77/onethirdcms)。使い方らしきものも書きました。

なぜ App Service の PHP8.1 を使わないか

App ServicePHP8.1で動かせばいいじゃないか、と思われるかもしれません。FTPSで公開用ディレクトリにオンラインインストーラ index.php を置くだけ済みそうです。

でもダメでした。

公開用のディレクトリ /homeAzure FilesCIFS/SMB をマウントして永続化していて、そのために POSIX のファイル操作の原子性や即時反映が期待通りに動作しなかったようです。(writableチェックとして、ファイルを複製して、元ファイルを削除、直後に再作成、でエラーになっている。ここまで念入りにチェックしていることにビックリした)

Webサーバも nginx を使っているようなので上記をクリアしてもおそらく mod_rewrite の問題で不可。

というわけで カスタムコンテナで PHP8.1 + Apache を動かす選択をするに至りました。コンテナ内のファイルシステムであればPOSIX準拠の動作になりますのでオンラインインストーラのチェックはクリアするかと。一方でファイルは一時ストレージに保持されることになるため、コンテナ停止時には /home へデータを退避は必要。

Azure App Serviceで動かす

Azure で リソースの作成>Web アプリ でリソースを作成します。公開方法を「コンテナ」、OSはLinux、価格プランを「Free F1(共通インフラストラクチャ)」を選びます。

App Serviceの設定をする

設定>環境変数

変数名設定値説明
WEBSITES_ENABLE_APP_SERVICE_STORAGEtrueApp Service機能。
コンテナ内 /home 配下が永続化されFTPでアクセスできる。
BACKUP_DIR/home/backupkomina77/onethirdcms 向け設定。
永続化対象のディレクトリを意識する。
BACKUP_CRON0 * * * *komina77/onethirdcms 向け設定。
cron書式。永続化のスケジュール。0 * * * * は毎時0分ごと。

設定>構成

  • FTPを使えるようにする。
    • SCM基本認証の発行資格情報をON
    • FTP基本認証の発行資格情報をON
    • FTPの状態をFTPSのみ
  • デプロイ>デプロイセンター>FTPS資格情報 に有効な値が表示されるのでFTPクライアントに登録する。

デプロイ>デプロイセンター>設定

項目設定値
ソースContainer Registry
コンテナの種類単一コンテナー
レジストリ ソースDocker Hub
リポジトリ アクセスパブリック
完全なイメージの名前とタグkomina77/onethirdcms:latest
スタートアップファイルまたはコマンド空欄
継続的デプロイOFF
WebhookURL空欄

準備作業

komina77/onethirdcms ではコンテナ起動時に ${BACKUP_DIR}/backup.sh 、コンテナ停止時に ${BACKUP_DIR}/restore.sh が実行されるようになっています。また、${BACKUP_CRON} が設定されていれば定期的に ${BACKUP_DIR}/restore.sh が実行されます。

komina77/onethirdcms では /var/www/html 配下がサイトとして公開されるようになっているので、${BACKUP_DIR}/data/var/www/html の間でファイルを同期するようなスクリプトを用意しておくことで公開資材を永続化することができるという仕組みです。

そこで、FTPクライアントから以下のスクリプトを保存したファイルを /${BACKUP_DIR} から /home を除いたディレクトリにアップロードしておきます。(${BACKUP_DIR}=/home/backup であれば、/backup/backup.sh, /backup/restore.sh という感じです)

#!/bin/sh

# 環境変数チェック
if [ -z "$BACKUP_DIR" ]; then
    echo "Error: BACKUP_DIR environment variable is not set"
    exit 1
fi

# ソースディレクトリ確認
SRC_DIR="/var/www/html"
if [ ! -d "$SRC_DIR" ]; then
    echo "Error: Source directory $SRC_DIR does not exist"
    exit 1
fi

# バックアップ先ディレクトリ作成
DEST_DIR="$BACKUP_DIR/data"
mkdir -p "$DEST_DIR"
if [ $? -ne 0 ]; then
    echo "Error: Failed to create backup directory $DEST_DIR"
    exit 1
fi

# バックアップ実行
echo "$(date '+%Y-%m-%d %H:%M:%S') - Starting backup: $SRC_DIR → $DEST_DIR"
rsync -a --delete "$SRC_DIR/" "$DEST_DIR/"
if [ $? -eq 0 ]; then
    echo "$(date '+%Y-%m-%d %H:%M:%S') - Backup completed successfully"
    exit 0
else
    echo "$(date '+%Y-%m-%d %H:%M:%S') - Error occurred during backup"
    exit 1
fi
#!/bin/sh

# 環境変数チェック
if [ -z "$BACKUP_DIR" ]; then
    echo "Error: BACKUP_DIR environment variable is not set"
    exit 1
fi

# バックアップソースディレクトリ確認
SRC_DIR="$BACKUP_DIR/data"
if [ ! -d "$SRC_DIR" ]; then
    echo "Error: Backup directory $SRC_DIR does not exist"
    exit 1
fi

# 復元先ディレクトリ確認/作成
DEST_DIR="/var/www/html"
mkdir -p "$DEST_DIR"
if [ $? -ne 0 ]; then
    echo "Error: Failed to create restore destination directory $DEST_DIR"
    exit 1
fi

# 復元実行
echo "$(date '+%Y-%m-%d %H:%M:%S') - Starting restore: $SRC_DIR → $DEST_DIR"
rsync -a --delete "$SRC_DIR/" "$DEST_DIR/"
if [ $? -eq 0 ]; then
    echo "$(date '+%Y-%m-%d %H:%M:%S') - Restore completed successfully"
    exit 0
else
    echo "$(date '+%Y-%m-%d %H:%M:%S') - Error occurred during restore"
    exit 1
fi

OneThirdCMSのインストール作業

OneThirdCMSを動かす環境が準備できたらインストール作業を行います。

  • OneThirdのダウンロードからオンラインインストーラをダウンロードし解凍する。
  • FTPクライアントから解凍したファイル index.php/${BACKUP_DIR}/data から /home を除いたディレクトリにアップロードする。
  • App Service の 概要 へ
    • 開始されてなかったら開始をクリック。
    • 参照をクリックすると公開されているURLが別タブで開く。このURLへのアクセスがトリガになってコンテナ起動となる。初回はコンテナのプルから始まるので時間かかります。
うまく準備ができていればブラウザに図のような画面が表示される

[Start Download OneThird CMS] ボタンをクリックするとダウンロードが走ります。

ダウンロードが終了するとインストールボタンが表示される

[Install OneThird CMS] ボタンをクリックすると資材が解凍されてインストールが開始されます。1項目ずつチェックが入り、見た目もかっこいいです。

今回はSQLiteを使うので [インストール(SQLite)] をクリック。

Admin ID や Admin password を適当に入れて、jQuery はCDN参照を選んでインストール開始します。

順調にインストールは進み・・・

成功したように思いきやなんかエラーが出てしまった。

うむ。私はPHP5までの人なのだが、どうやら PHP8 で非推奨になった書き方についてのエラーのようだ。PHP8.1からさらにPHP7系に戻すのもセキュリティ的に問題ありですし(PHP8.1もセキュリティサポート切れてますが)、ソースを直してまで稼働するところまで持っていく義理もなく。。

残念だがここで中断することにした。おそらくこういった非推奨エラーになるところを一つ一つ潰していけば、この興味深いCMSは動くようになると思われる。

(よくよく見るとローカルPCで動かす版のPHPもバージョン7のようだ :-))

中途半端になってしまったが、無駄にAzure App Serviceの知識が増えたところで今回はあきらめることにした。長々と読んでいただいた方、結果がパッとせず申し訳ない。

]]>
/archives/1610/feed/ 0
FreeCAD v1.0 でなんか書く (4) /archives/1570/ /archives/1570/#respond Tue, 08 Apr 2025 15:19:32 +0000 https://www.komina.info/?p=1570 (1)で1×6材、2×6材は、Fillet 処理を省略してきました。組立工程が一段落したのでFillet 処理を施してみます。

側板のFillet 処理

  • 側板の Body > Pad を選択して、Filletボタンを押下する。

簡単ですが2×4材っぽくなりました。

棚板の Fillet処理

  • 同様に Pad を選択して、Filletボタンを押下する。

組立図を見る

部品の方の変更はそのまま組立図の方に反映されます。Fillet処理がされると急にリアル感が増しますね。

着色してみる

さらにリアルにするために部品に色を付けていきます。

  • 側板のボディを右クリックする。
  • メニューから「外観…」をクリックする。
  • 「外観をカスタマイズ」をクリック。
  • マテリアル・プロパティのダイアログが開くので「散乱光の色」をクリック。
  • 色を選択のダイアログが開くので色を選択する。
  • 同じように、棚板や取付金具の色を変更していく。
  • 色を選択する方法以外に、プリセットされた素材を選択する方法もある。便利。

塗装を考えているときは、配色をシミュレーションできますね。

次はアセンブリワークベンチの機能を使ってみたいと思います。

]]>
/archives/1570/feed/ 0
FreeCAD v1.0 でなんか書く (3) /archives/1485/ /archives/1485/#respond Fri, 04 Apr 2025 15:54:16 +0000 https://www.komina.info/?p=1485 構成する部品が一通り準備できたのでFreeCADのV1.0で標準で同梱されるようになった Assemblyワークベンチを使って組み立ててみたいと思います。

  • 新規作成、「ディスプレイシェルフ.FCStd」でファイルを保存。
  • Assemblyワークベンチに切り替える。
  • アセンブリ作成ボタン押下、さらにコンポーネント挿入ボタン押下する。
  • ファイルを開き、先ほど作成した部品コンポーネントを開く。
  • そしてまず、基準となる部品のボディを挿入する。
  • 最初の部品を挿入すると基準パーツにするかを問うダイアログが出てくるので、Yesボタン押下する。
  • 引き続き、棚板を1枚挿入する。
  • 側板は基準となるパーツなのでカギマークが中心に描かれている。(非表示も可能)
  • 棚板は側板に埋まった状態になる。
  • ツリー状には挿入したパーツのラベル名が表示されるので、必要に応じて分かりやすく変更する。
  • ツリーから棚板を選ぶと移動・回転するためのXYZ軸の矢印が出てくる。(ギズモ、と呼ぶらしい?)
  • まずはこの矢印を使っておおよその位置関係に移動する。
  • 組立は様々なジョイントを使用する。
  • まず側板に対して棚板が上下にスライドして高さ調整するイメージで、接する辺をそれぞれ選択した状態とする。
  • スライダージョイントを作成ボタンを押下する。
  • 2つの辺とスライダーという情報だけでジョイントが作成される。
  • タスクにて、回転角度やオフセット値を変更して希望の位置関係に修正する。
    ここでは回転=-90°とする。
  • OKを押してジョイント完了。
  • 棚板をドラッグして上下に動かせる状態となる。
  • 最小長さ=最大長さ、にすると固定状態となる。
  • 棚の高さは組み立て後に調整することにして、同じように棚板を追加していく。
  • 反対側の側板を追加する。
  • 側板のジョイント方法1
    • 側板_右と側板_左を固定ジョイント、オフセットを棚板の長さ700mmで固定する。
  • 側板のジョイント方法2
    • 棚板とスライダージョイント。距離ジョイントで側板の左右で高さが一致するようにする。
  • 後者であれば側板の長さが変わったときに対応できそう。前者であっても変数を使っておけば対応できそう。
  • ここでは前者の方法(固定ジョイント)を採用。
  • 側板の内側の面をそれぞれ選択して、固定ジョイントを押下する。
  • 選択した面同士がピタっとくっつくので、オフセット値を調整する。

棚の全貌が見えてきました。次はL字取付金具を付けていきます。

取付金具は木材の面に接することを条件に固定していきます。

  • らくらく取付L字金具を1つ追加する。
  • 組み立て方の画像のように向きと位置を合わせる。
  • 代替の位置が決まったら、作業しやすいように「側板_左」を非表示にする。
  • 「側板_左」と接する棚板の側面と、取付金具の面を選択する。
  • 距離ジョイント(距離0mm)を押下する。
  • だいたい望んだとおりに配置されないので、落ち着いて向きを調整する。
  • 取付金具の方のオフセットを調整するので、Offset2のボタンを押下する。
  • 取付金具に表示されているギズモを見て調整していく。
  • 今回の場合は向きは問題ないので位置調整を行う。
    • 赤のX軸の方向で手前に移動。
    • 緑のY軸の方向で下へ移動。
  • 次は棚板の底面と取付金具の面を選択する。
  • 同じように距離ジョイント(距離0mm)。
  • 側板との距離ジョイントを保持しつつ、棚板の底面とピタリとくっついた。
  • 思惑通りになったので今回は調整は不要。
  • さらに棚板の前面と取付金具の面を選択し、同様に距離ジョイント(0mm)。

こちらも棚板の前面にピッタリくっつきました。ここで「側板_左」を表示状態に戻してみると、いい感じに組みあがっていることが分かります。

取付金具は棚板に固定されているので、棚板をドラッグして上下させると、取付金具も一緒に動くことが確認できますよ。

  • あとは側板の左右、棚板の分だけ取付金具を挿入&固定していく。
  • 完成形に近づいてきた。

上部にアジャスターを装着します。

  • 2×4材用アジャスターを追加。
  • 移動して「側板_右」の上部に配置する。
  • 側板の上面と、アジャスターの面を選択して固定ジョイント。
  • 2×4材用アジャスター、側板、それぞれ作図したときに原点=中心になるようにしたので固定ジョイントだけでいい感じの場所に固定された。
  • ハイグリップアジャスターを追加。
  • 移動して「2×4材用アジャスター_右」の上部に配置する。
  • ハイグリップアジャスターの軸と、2×4材用アジャスターのナット穴の円筒を選択する。
  • 円筒ジョイントを押下する。
  • ネジ穴にハイグリップアジャスターがスポッと固定されればOK。
  • ハイグリップアジャスターをドラッグして上下すると、ネジが回転しながら上下する。すごい!
  • 左側の側板についても同じようにアジャスターを固定する。

これで一通り組み上げることができました。ツリーはこんな感じになりました。

私なりに調べて試して何とか形になりましたが、正直これが正解なのか分かりません。。参考になりましたら幸いです。

]]>
/archives/1485/feed/ 0
FreeCAD v1.0 でなんか書く (2) /archives/1423/ /archives/1423/#respond Fri, 04 Apr 2025 15:53:55 +0000 https://www.komina.info/?p=1423 2×4材用アジャスター

続けて2バイ用のアジャスターを書いていきます。早速サイズが分かる画像を検索してみたのですが・・・。(実際に購入する際はネジや滑り止めゴムシートが一緒になったセットが販売されています)

[商品価格に関しましては、リンクが作成された時点と現時点で情報が変更されている場合がございます。]

木材用アジャスター [8個]
価格:1,514円~(税込、送料別) (2025/4/3時点)


簡単な図だけでネジ穴の位置が詳細にわかる画像が見つかりません。そこで正攻法でメーカーHPをあたってみたとこr、製品の電子カタログ(PDF)を発見しました。商品ページに書かれているJANコードで検索すると詳細な図面にたどり着けました。これに従って書いていきます。

  • 新規作成。ファイル名は「2×4材用アジャスター.FCStd」。
  • ボディ作成、スケッチをXY平面で作成。
  • 中央の部分から板を生成して、Sheet Metalで耳を伸ばしていく方針。

さて耳はどうしようか、と思ったら図面には曲げRが記載されておらず困りました。門外漢なのでどんな値が適当なのかわかりません。先ほど書いた金具では、板厚1.2mmでR0.5mmでした。今回は板厚1.6mmということなので分かりやすくR1.0mmと仮定して進めます。耳の角丸もR4mmと仮定します。

  • D=39mm、曲げ(R1.0)を考慮して 39-1.0-1.0=37mm。
  • スケッチを閉じて Padで逆方向に押し出す。
  • Sheet Metal ワークベンチに切り替える。
  • 耳を作成する辺を選択して、Make Wall ボタンをクリック。
  • 曲げRは1.0mmのまま。壁の高さは板厚1.6mmと曲げ1.0mmを含んで25mmなので、25mm-1.6mm-1.0mm=22.4mm。
  • 片耳ずつスケッチを作成して抜き(ネジ穴、角丸)を書く。
  • スケッチを閉じて Pocket押下してタイプ「最初まで」で抜く。
  • 次はひっくり返して鉄製高ナットをくっつける。
  • スケッチを作成。
  • M10の高ナット(32mm)は Fasteners ワークベンチではやり方が分からなかったので自作することにする。
  • 正六角形を書いて、辺の並行拘束、サイズを13mmに設定してスケッチを閉じる。
  • Padで32mm押し出す。
  • それっぽく見えればいいので、押し出した面でスケッチを作成。
  • M10めねじ内径8.376mmを直径とする円を書いて Pocket で 32mm 掘る。

省略しましたけど雰囲気は再現できたように思います。

ハイグリップアジャスター

最後にハイグリップアジャスター M10×43.5という部品を書きます。


例によって電子カタログから数値データを見つけてきました。

  • ツマミの部分から書く。
  • 円から円柱の流れで書いていきたいところだけど、鏡餅みたいな断面になっているので断面を書いて回転させることにする。
  • ツマミのトータル高さは表から9.5mm。ギザギザのある部分の高さを仮に4.5mmとし、モリモリの円弧半径を2mm2mmとした。
  • この断面図のスケッチを閉じて、Revolution 押下。設定はデフォルト。
  • それっぽい形になってれば成功。
  • 次はツマミにギザギザを付けていく。(ローレット加工というらしい)
    • ツマミの円周に食い込むように三角柱をぐるっと配置して減算する流れ。
  • 新しいボディを作成し、XY平面でスケッチを作成する。
  • チビっとだけ円周に食い込む正三角形を書く。(サイズや位置は適当でOK、現物合わせ)
  • スケッチを閉じ、Pad 押下。
  • ボディを選択した状態で Part Designワークベンチの PolarPatternを押下する。
    円状パターンパラメータの回数を
  • 真上からの角度にするとわかりやすい。
  • 回数=64回にしてみると、ツマミの写真と近い感じになったので確定。
  • Part ワークベンチに切り替える。
  • ツマミ → 三角柱 の順にボディーを選択し、Cut(切り取り)をクリック。
  • ツマミから三角柱が切り取られた図形が作成される。
  • 次はネジ部分。見やすくするためにツマミの方は非表示にする。
  • 楽するため、Fasteners ワークベンチを使う。
  • まず、Add DIN ThreadedRod Metric を押下する。
    • DiameterはM10
    • Pich Customは1.5mm、ThreadはTrueに。
    • Lengthはツマミに少し埋めるつもりで長めに45mmとしておく。
  • ネジ部分を非表示にする。
  • Add ISO 4035 Hexagon thin nuts 押下。
    • DeiameterはM10
    • ThreadはTrue
  • ツマミ、ネジ部、ナットをすべて非表示→表示へ。
  • Part ワークベンチに切り替える。
  • まずツマミとナットが完全に重なっているので、ナットを移動させる。
  • ツリーからナットを選択して右クリック>変換。
    するとXYZの矢印が表示されるので、Z軸(青矢印)に沿って動かしてツマミ内部から外へ出す。
    動かす量は好みでOK。
  • 次はネジ部分が43.5mmとなるように、ツマミの中に埋め込む。
  • ネジ部分は45mmで作成したので、1.5mm上へ動かせばよい。
  • 最後にツマミ、ネジ部分、ナットを一つの立体へ統合する。
  • ツリーでツマミ、ネジ部、ナットをすべて選択し、Fuse(結合)を押下する。
  • Fusionという一つのオブジェクトに統合されて完成。

部品がすべて用意できたので組み立ててみたいですね。

]]>
/archives/1423/feed/ 0
FreeCAD v1.0 でなんか書く (1) /archives/1372/ /archives/1372/#respond Fri, 04 Apr 2025 15:53:32 +0000 https://www.komina.info/?p=1372 FreeCADに少し慣れてきたような気がしてきたので、DIY用途を想定して何か書いてみようと思ったのだった。

部品の形状も点数もほどよい感じの下記の棚を作ってみることにする。

1×6材(700mm)

直方体なので簡単です。

  • 新規作成、Part Design ワークベンチに切り替える。
  • ボディーを作成
  • XY平面でスケッチを作成
  • 1×6材の断面(19mm×140mm)の長方形を原点を中心に書く。
  • スケッチを閉じて、Pad機能 で700mm押し出して直方体を作成する。

棚板に相当する部品ができたので保存。Fillet 処理などすると見栄えが良くなるけど、組立工程でごちゃごちゃしないように現時点ではこのままとしておく。

2×6材(2350mm)

棚板と同じように側板も作成する。

  • 新規作成、Part Design ワークベンチに切り替える。
  • ボディーを作成
  • XY平面でスケッチを作成
  • 2×6材の断面(38mm×140mm)の長方形を原点を中心に書く。
  • スケッチを閉じて、Pad機能 で700mm押し出して直方体を作成する。
  • 保存。

2×4材用ラクラク取付L字 バイ6用

取付L字金具を作る。商品名で画像検索すると詳細な図面が見つかったのでそれを参考にする。

[商品価格に関しましては、リンクが作成された時点と現時点で情報が変更されている場合がございます。]

2×材用らくらく取付L字 [20個]
価格:2,640円~(税込、送料別) (2025/3/31時点)


  • 新規作成、Part Design ワークベンチに切り替える。
  • ボディーを作成
  • XY平面でスケッチを作成
  • 上の部分を書いてみる。板厚1.2mm、曲げ R0.5mm。なのでパラメトリック設計ということで式で 38-1.2-0.5=36.3mm、138-1.2-0.5=136.3mmと入力した。(オレンジ色表記)
  • Pad で板厚1.2mm、逆方向に押し出します。
    逆方向なのは組み立て時にXY平面にツーバイ材が面するときを想定してです。不要かもしれません。
  • 次に耳を作成していく。
  • Sheet Metal ワークベンチに切り替える。(拡張機能の追加でインストール必要)
  • 耳をはやす面を選択して Make Wallボタンを押下する。
  • 耳はトータルで18mm。
    曲げR 0.5mm、板厚1.2mm であることから逆算して、18.2-1.2-0.5=16.5mmとした。
  • Part Design ワークベンチに切り替える。
  • 耳の側面を選択してスケッチを作成。
  • 板から抜きたい部分(ネジ穴と角丸)を書く。
  • スケッチを閉じ、Pocket で抜く。タイプは「最初まで」にしておくと板厚に影響を受けない。
  • 次はもう一つの側を作る。
  • 再び Sheet Metal ワークベンチに切り替える。
  • 曲げる部分のラインを選択し Make Wall ボタンを押下。
  • トータルで38mm。耳の部分と同じように考えて、曲げR 0.5mm、板厚1.2mm であることから逆算して、38-1.2-0.5=34.3mmとした。
  • 同じように耳を作る。先ほどのパラメータと同じく、
    曲げR 0.5mm、板厚1.2mm であることから逆算して、18.2-1.2-0.5=16.5mm。
  • スケッチを作成し、板から抜きたい部分(ネジ穴と角丸)を書く。
  • Pocket を押下。タイプは「最初まで」
  • 先ほどの耳と同じようにトータルで18mm。
    曲げR 0.5mm、板厚1.2mm であることから逆算して、18.2-1.2-0.5=16.5mmとした。
  • スケッチを作成し、板から抜きたい部分(ネジ穴と角丸)を書く。
  • Pocket を押下。タイプは「最初まで」
  • 保存して完成。

次でアジャスターを書いていきます。

]]>
/archives/1372/feed/ 0
java6のzip問題 /archives/1234/ /archives/1234/#respond Thu, 05 Dec 2024 16:04:31 +0000 https://www.komina.info/?p=1234 私、仕事でいまだにjava6が生きている環境を扱っております。維持だけでなく時折補助ツールを作ったりもします。

言うてもjavaなので新しい書式を使ったり新しい機能を使おうと思わない限りは問題はほとんど起きないものです。そんな中で ZipOutputStreamで意外な原因で以下の例外が発生することを発見しました。

せっかくなのでメモしておきます。

そもそも ZipOutputStreamFileOutputStreamなどのストリームに接続してZip圧縮したバイナリを出力するものです。ZipOutputStreamオブジェクトに対して putNextEntry(ZipEntry e) メソッドでファイルやディレクトリを追加していく感じで使います。で、最後にcloseすると無事Zipアーカイブが完成するというわけです。

意外な原因とは

どこら辺が問題かというと、ZipOutputStreamオブジェクトを生成しておきながら一件も putNextEntryしないで closeすると例外が発生するという。。。

つまり中身が空っぽのZipファイルを作成できないということなのです。

えー、そんなことが想定されていなかったの?という感じでした。さすがにjava7からは修正されていますので、私のようなjava6縛りプレイを強いられている方以外は新しいjavaを使うことで回避できます。。

解決策

ではどうするか。まず、圧縮対象が見つかったタイミングで ZipOutputStreamオブジェクトを生成するようなラップクラスを作ります。(ZipOutputStreamクラス自体は継承できないので新たに包含したクラスを作成するイメージです。デザインパターンでいうところのBridgeパターンでしょうか、知らんけど)

ここでは ZipCreaterJava6 というクラスを作成しました。こんな感じです。

public class ZipCreaterJava6 {
    FileOutputStream os = null;
    ZipOutputStream zos = null;
    
    // 空ZIPバイナリ
    int[] emptyzip = { 'P', 'K', 5, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
    
    public ZipCreaterJava6(File zipFile) throws Exception {
        this.os = new FileOutputStream(zipFile);
    }
    
    public void putNextEntry(ZipEntry e) throws IOException {
        getZos().putNextEntry(e);
    }
    
    public void write(byte[] b, int off, int len) throws IOException {
        getZos().write(b, off, len);
    }

    void close() throws IOException {
        if (this.zos == null) {
            // Create an empty ZIP file.
            for (int e : emptyzip) {
                os.write(e);
            }
        } else {
            zos.close();
        }
        if (os != null) os.close();
    }
    
    ZipOutputStream getZos() {
        if (zos == null) zos = new ZipOutputStream(os);
        return zos;
    }
}

putNextEntry メソッド、writeメソッド、 closeメソッド を実装しますと、ZipOutputStreamっぽく使えます。 putNextEntryメソッド、writeメソッド内では getZos()経由することで初回呼び出しを検出してそのタイミングでZipOutputStreamオブジェクト生成しています。
closeメソッド内ではZipOutputStreamオブジェクトが生成されていれば close処理する。生成されてなかったら空っぽのZIPを表すバイナリ列を返すようにします。

使用サンプルは以下のようになります。

public class Main {

	public static void main(String[] args) throws Exception {
		File dir = args.length > 0 ? new File(args[0]) : null;
		if (dir != null && dir.exists() && dir.isDirectory()) {
            ZipCreaterJava6 zos = new ZipCreaterJava6(new File("archive.zip"));
            //ZipOutputStream zos = new ZipOutputStream(new FileOutputStream("archive.zip"));
		    
    		for (File item : dir.listFiles()) {
    		    FileInputStream fis = new FileInputStream(item);
    		    ZipEntry entry = new ZipEntry(item.getName());
    		    zos.putNextEntry(entry);
    		    
    		    byte[] buffer = new byte[4096];
    		    for (int n; (n=fis.read(buffer)) != -1; ) {
                    zos.write(buffer, 0, n);
                }
    		    fis.close();
    		}
    		zos.close();
		} else {
		    System.out.println("第一引数にソースとなるディレクトリを指定してください.");
		}
	}
}

最後に

どうしてもjava6でも動くようにしたい、という奇特なケースでない限りは不要なコードはありますが、実際私のような人間も2024年の時点で存在しているわけでして。あちこちから集めた情報をもとに解決した内容をここに記しておきました。

例外処理とかは適当です。引用するときはちゃんと書きましょう。以上。

]]>
/archives/1234/feed/ 0
[windows10] クリップボードが使えない? /archives/1225/ /archives/1225/#respond Wed, 25 Sep 2024 03:48:29 +0000 https://www.komina.info/?p=1225 原因を探す

ある日、CTRL+C してもクリップボードにコピーされない事象が発生しました。

サクラエディタにはクリップボードに何かコピーされるとツールボックスの貼付アイコン

が活性化されるので、これを利用して状況を確認。どうやら一瞬だけ活性化されるようなので、何らかのアプリ?サービス?がクリップボードをクリアしているようです。どんなときにクリアされるのか、PCを再起動して普段使っているアプリケーションを1個ずつ起動しながら、クリップボードが使えなくなる条件を見極めていくことにしました。

で、見つかりました。リモートデスクトップクライアントを特定の接続先に接続すると事象が発生しました。

リモートデスクトップには、ローカルPCと接続先PCとの間でクリップボードを共有する機能があります。これを実現するために接続先PCには「RDP クリップボード モニター」というプログラムが起動しています。このプログラムの調子が悪くなっていたのが原因でした。

対処方法

対処としては、接続先PCで動いている「RDP クリップボード モニター」を再起動すること、になります。タスクマネージャでプロセスタブで「RDP クリップボード モニター」を探して、あるいは詳細タブで「rdpclip.exe」を探してタスクの終了を行います。

その後に、タスクマネージャのファイル>新しいタスクの実行から「rdpclip.exe」を実行してください。

参考

]]>
/archives/1225/feed/ 0