「第1回 PHP 8 上級 模擬試験」を解説していきます
本記事ではひたすら下記記事の模擬試験の解説をしていきたいと思います!
第1回 PHP 8 上級 模擬試験
https://study.prime-strategy.co.jp/study/ph8ex1/
本記事ではひたすら下記記事の模擬試験の解説をしていきたいと思います!
第1回 PHP 8 上級 模擬試験
https://study.prime-strategy.co.jp/study/ph8ex1/
Contents
本記事ではひたすら下記記事の模擬試験の解説をしていきたいと思います!
第1回 PHP 8 上級 模擬試験
https://study.prime-strategy.co.jp/study/ph8ex1/
Contents
**SCF(Smart Custom Fields)**は、WordPressサイトに高度なカスタムフィールド機能を追加するためのプラグインです。これにより、投稿やページ、カスタム投稿タイプに対して多様なデータを簡単に追加・管理することができます。SCFは、その柔軟性と使いやすさから、初心者から上級者まで幅広いユーザーに支持されています。
SCFを導入する手順は以下の通りです:
項目 | 内容 |
---|---|
繰り返し | フィールドを複数回使用する必要がある場合にONに設定します。 |
タイプ(必須) | 入力させたいデータの形式を選択します。テキスト、画像、日付など多様なオプションから選べます。 |
ラベル | カスタムフィールドの内容を簡潔に表現する名称を入力します。例として「価格」や「写真」などが挙げられます。 |
名前(必須) | フィールドを表示する際に使用する識別子で、半角英数字およびハイフン(-)・アンダースコア(_)のみで設定します。例:price , recipe_image |
デフォルト | フィールドに初期値を設定したい場合に入力します。ユーザーが入力しなくてもこの値が自動的に表示されます。 |
手順(※) | 特定の手順が既に決まっている場合、その内容をここに記載します。例:調理手順のステップバイステップ説明。 |
メモ(※) | 補足情報や注意事項を記入するフィールドです。例:追加の説明や特記事項など。 |
「Custom Post Type UI」プラグインを使用するか、functions.phpに記述するか2パターンあります
下記の通りカスタム投稿タイプを作成
functions.php
<?php
add_action('init', 'custom_posttype');
function custom_posttype()
{
register_post_type('recipe', array(
'labels' => array(
'name' => 'レシピ', // 管理画面のメニューに表示される名前
'singular_name' => 'レシピ', // 管理画面のメニューに表示される名前(単数形)
),
'public' => true, // 一般公開(フロント)・管理画面共に利用
'show_ui' => true, // 管理画面にメニューを表示
'rewrite' => true, // WordPress側でパーマリンクが自動で設定される、例えば「http://example.com/recipe/recipe-name」
'has_archive' => true, // アーカイブページを持つ
'hierarchical' => true, // 固定ページのように親子関係を持つ
'menu_position' => 5, // 管理画面のメニューの位置
'supports' => array('title'), // タイトルのみ(他はカスタムフィールドで設定)
'show_in_rest' => true, // REST API、Gutenbergでの表示を有効にする
));
}
「SCR」側の設定で「表示条件」で作成したカスタム投稿タイプを有効化
WordPress では「single-{投稿タイプのスラッグ}.php
」という名前のテンプレートファイルがあると、
その投稿タイプの単一投稿ページ(個別ページ)を表示する際に使われるようになります。
カスタム投稿タイプの登録コードを変更したとき、あるいは新規に追加したときには
設定画面 > パーマリンク設定 を一度開いて「変更を保存」する必要があります。
(実際には押すだけでOK。これでリライトルールが再生成されます)
WordPress管理画面で「Smart Custom Fields」の新規作成で下記の通りカスタムフィールドを作成
今回は「よくある質問(FAQ)」コンテンツ作成したいので、繰り返しを有効化しました
functions.phpに下記コードを追加
<?php
// よくある質問 オプションページ追加
SCF::add_options_page('よくある質問', 'よくある質問', 'manage_options', 'faq-options');
各引数の意味SCF::add_options_page( $page_title, $menu_title, $capability, $menu_slug )
$page_title
$menu_title
$capability
manage_options
となっているため、通常は管理者(Administrator)のみがアクセス可能になります。$menu_slug
'faq-options'
が指定されているので、管理画面URLの末尾に ?page=faq-options
の形でアクセスできるようになります。管理画面のメニューに作成したオプションページが追加されます
作成したオプションページのデータをテーマテンプレートで表示する手順は以下の通りです。
<?php
if (function_exists('scf')) {
// 例:サイトロゴの表示
$site_logo = scf::get('site_logo', 'options'); // 'options'はオプションページを指定
if ($site_logo) {
echo '<img src="' . esc_url($site_logo) . '" alt="サイトロゴ">';
}
// 例:連絡先情報の表示
$contact_email = scf::get('contact_email', 'options');
if ($contact_email) {
echo '<p>お問い合わせ: <a href="mailto:' . esc_attr($contact_email) . '">' . esc_html($contact_email) . '</a></p>';
}
}
?>
~/.ssh/
│
├── config # SSH接続設定ファイル(テキスト)
│ # 複数サーバーの接続設定を記述可能
│
├── id_rsa # 秘密鍵
│ # 権限: 600(所有者のみ読み書き可)
│ # 絶対に共有しない
│
├── id_rsa.pub # 公開鍵
│ # 権限: 644(所有者は読み書き可、他者は読み取りのみ)
│ # サーバーに登録する
│
└── known_hosts # 接続したことのあるサーバーの情報
# 初回接続時に自動生成
# デフォルトの設定(すべてのHostに適用)
Host *
# タイムアウトを設定(秒)
ServerAliveInterval 60
# 圧縮を有効化
Compression yes
# 開発サーバーの設定例
Host dev
# サーバーのホスト名またはIPアドレス
HostName dev.example.com
# SSHのポート番号(デフォルトは22)
Port 22
# ログインするユーザー名
User developer
# 使用する秘密鍵の場所
IdentityFile ~/.ssh/id_rsa
# ステージングサーバーの設定例
Host stg
HostName stg.example.com
Port 22
User deployer
# 別の秘密鍵を使用する例
IdentityFile ~/.ssh/staging_key
# 本番サーバーの設定例
Host prod
HostName prod.example.com
# セキュリティのため別ポートを使用
Port 10022
User production
IdentityFile ~/.ssh/production_key
# パスワード認証を無効化
PasswordAuthentication no
~/.ssh/id_rsa
の秘密鍵は以前作成したものを使いまわします
公開鍵を張り付ける
id_rsa.pub
が公開鍵ファイルになります。このファイルの内容をXserverの管理画面にコピー&ペーストすることで、先ほどの秘密鍵(id_rsa
)と対応する公開鍵認証が設定されます。
.ssh/configファイルを使ってssh接続できるようになります:
ssh xserver
SSHコマンドが設定ファイルの読み込みから認証まで、多くのことを自動的に処理してくれます。
Contents
「Ctrl + ?」でキーボードショートカットウィンドウを表示確認が可能です
1)「表示形式」→「条件付き書式」
2)範囲に適用にグレーアウトするセルの範囲を入力
3)書式設定の条件のプルダウンから「カスタム数式」を選択
4)=$A3=trueと数式を入力
※複数チェックボックスにする場合 →=AND($A1=TRUE, $B1=TRUE, $C1=TRUE)
B列に入力されるとA列に1から番号付与していきます
1)if(B4<>””,1,””)とA4に入力
if(条件,trueの式,falseの場合の式
2)if(B5<>””,A4₊1,””)とA5に入力
3)A5セルをドラッグして下まで同様に式を適用
スプレッドシートのARRAYFORMULA()は、複数のセルに対して一度に計算を行うための強力な機能です。
主な特徴:
基本的な使い方:
例えば:
=ARRAYFORMULA(A1:A10 * 2)
これはA1からA10までの各セルの値を2倍にし、結果を10個のセルに出力します。
=VLOOKUP(検索値, 検索範囲, 列番号, [検索方法])
同じスプレッドシート内の別シートを参照する場合、シート名を指定し、感嘆符(!)を使用して範囲を指定します。
=VLOOKUP(検索値, シート名!範囲, 列番号, [検索方法])
例:
Copy=VLOOKUP(A2, Sheet2!$A$1:$C$10, 2, FALSE)
この例では、現在のシートのA2セルの値を、”Sheet2″という名前のシートのA1:C10範囲で検索します。
別のスプレッドシートファイルを参照する場合、IMPORTRANGE関数と組み合わせて使用します。
まず、IMPORTRANGE関数で別ファイルのデータを取り込みます:
=IMPORTRANGE("スプレッドシートのURL", "シート名!範囲")
そして、これをVLOOKUP関数の検索範囲として使用します:
=VLOOKUP(検索値, IMPORTRANGE("スプレッドシートのURL", "シート名!範囲"), 列番号, [検索方法])
例:
=VLOOKUP(A2, IMPORTRANGE("https://docs.google.com/spreadsheets/d/abcdefghijklmnop", "Sheet1!A1:C10"), 2, FALSE)
この例では、別のスプレッドシートファイルのSheet1のA1:C10範囲をインポートし、その中でA2セルの値を検索します。
注意点:
初期状態のシートは26列(Z列まで)しかありません
右端の列で増やしたい数だけ行をコピーし、右クリックして「特殊貼り付け」→「転置して貼り付け」
Contents
- 修正したコードでどんな理由で何をしているかのコメントアウトで詳しく説明を加えてください
- 下記の通り修正履歴もコメントアウトで残してください
/**
* 修正履歴コメントのサンプル
*
* 文字化け解消のための修正履歴:
* 1. Content-Typeヘッダーの設定を試行
* res.setHeader('Content-Type', 'application/json; charset=utf-8');
*
* 2. Buffer経由のデコードを試行
* Buffer.from(rows[0].content).toString('utf8')
*
* 3. クエリでのCONVERTを試行
* SELECT CONVERT(content USING utf8mb4) as content
* SELECT CONVERT(CAST(content AS BINARY) USING utf8) as content
*
* 4. 現在の修正案:セッション文字コードの明示的な設定と
* バイナリ経由での文字コード変換
*/
プロジェクトの構成ファイルやディレクトリを確認する場合
Get-ChildItem -Path . -Depth <数字> -Force
# サブディレクトリのファイルも表示する場合
tree /f
# 特定のファイルタイプを除外する場合(例:.gitを除外)
tree /f /a | findstr /v ".git"
質問の要点を整理しきれていないときでも、まずは自分が今どんな作業・状況にいるかを共有するだけで、相手がヒントを出しやすくなります。
たとえば「○○の機能を実装中なんですが、動かしてみるとエラーが出ます。エラー内容を一通り検索してみたんですが、似たような事例は見当たりませんでした」といった形で簡単に状況を説明しましょう。
まだうまく言語化できていない段階でも、「分かっている点」と「分からない点」をざっくりと区別できるよう意識しましょう。
「原因は不明だけど、エラーが出るタイミングは分かっている」「作業手順は知っているが、どこに注目すればいいかピンとこない」など、断片的でも構いません。 これだけで相手が「ここから話を聞けばよさそうだな」と思いやすくなります。
質問者である自分自身が混乱しているときは、「どこが分からないのかよく分からなくなってきました」という事実をそのまま伝えてしまうのも有効です。 相手は慣れている可能性が高いので、「では一度、○○から整理してみましょうか」とステップ・バイ・ステップで導いてくれるでしょう。
言い回し例 | ポイント |
---|---|
「○○の機能を触っているのですが、どうにもエラーが出て原因が分からないです。エラー文は△△で、検索してもヒントがなくて…まず何を確認したらよいでしょうか?」 | 具体的に「エラー文」や「すでに調べた内容」を伝えると、相手が“次の調査ポイント”を提案しやすくなる |
「実は、どこが分からないのか自分でもはっきりしないんです。おそらく□□あたりが怪しいと思うのですが、何が論点なのか一緒に整理していただけると助かります」 | 「整理してほしい」「いっしょに考えてほしい」というリクエストを明確にすることで、相手は段階を踏んだ説明をしやすくなる |
「最終的に××機能を完成させたいのですが、どういう手順や知識が必要かイメージできていません。まずはどのあたりから手をつければいいでしょうか?」 | ゴールを示しつつ、段階的な手引きを求める言い方。相手の経験を生かしたアドバイスを得やすい |
「あいまいな部分が多くてすみません。もし▲▲や■■について先に理解しておいたほうがいい場合は教えてください。自分でもあわせて勉強します」 | 事前知識の不足を自覚していることを伝えると、相手は必要なキーワードや前提知識を提案しやすくなる |
Contents
当初、VBA Formatterプロジェクトで日本語を含むプロンプトデータを表示した際に文字化けが発生していました。
具体的には「あいうえお」が「縺ゅ>縺」のように表示される状態でした。
文字コードとは、コンピュータが文字を扱うための約束事です。
コンピュータは内部的には全て数値(バイナリ)で処理するため、
「あ」という文字を「あ」として認識するためには、決まった規則が必要になります。
文字化けの主な原因は、データの流れる過程で文字コードの解釈が一貫していなかったことです:
これらの設定が異なると、例えば:
というような不整合が発生し、文字化けの原因となります。
Node.js(Express)とMySQLをDockerで構築する手順を、順を追って解説します。
-- データベースの文字コード設定
SET NAMES utf8mb4;
SET CHARACTER SET utf8mb4;
-- テーブル作成時の設定
CREATE TABLE prompts (
-- カラム定義
) CHARACTER SET utf8mb4
COLLATE utf8mb4_unicode_ci;
// データベース接続設定
const config = {
charset: 'utf8mb4',
collation: 'utf8mb4_unicode_ci'
};
// クエリ実行時の設定
await db.query("SET NAMES utf8mb4");
// バイナリデータとして受け取り、適切にデコード
const buffer = await response.arrayBuffer();
const decoder = new TextDecoder('utf-8');
const jsonString = decoder.decode(buffer);
project/
├── docker-compose.yml
├── Dockerfile
├── package.json
├── .env
└── src/
├── index.js
├── bedrock/
│ ├── analyzer.js
│ └── client.js
├── db/
│ ├── connection.js
│ └── init/
│ └── 01-schema.sql
└── public/
├── index.html
├── style.css
└── script.js
version: '3.8'
services:
app:
build: .
ports:
- "4000:4000"
volumes:
- .:/usr/src/app
- /usr/src/app/node_modules
environment:
- PORT=4000
- DB_HOST=db
- DB_USER=vbauser
- DB_PASSWORD=vbapassword
- DB_NAME=vba_formatter
depends_on:
- db
db:
image: mysql:8.0
platform: linux/amd64
# 文字コードの設定:日本語を正しく扱うためのMySQLサーバー設定
command:
- --character-set-server=utf8mb4
- --collation-server=utf8mb4_unicode_ci
ports:
- "3307:3306"
environment:
MYSQL_ROOT_PASSWORD: rootpassword
MYSQL_DATABASE: vba_formatter
MYSQL_USER: vbauser
MYSQL_PASSWORD: vbapassword
TZ: Asia/Tokyo
volumes:
- mysql_data:/var/lib/mysql
- ./src/db/init:/docker-entrypoint-initdb.d
volumes:
mysql_data:
FROM node:20-slim
WORKDIR /usr/src/app
# ロケールのインストールと設定 (Debianベース)
RUN apt-get update && apt-get install -y locales && \
sed -i 's/# ja_JP.UTF-8/ja_JP.UTF-8/' /etc/locale.gen && \
locale-gen ja_JP.UTF-8 && \
apt-get clean
ENV LANG=ja_JP.UTF-8
ENV LC_ALL=ja_JP.UTF-8
ENV LANGUAGE=ja_JP:ja
# MySQLクライアントパッケージの名前を修正
RUN apt-get update && apt-get install -y default-mysql-client
# アプリケーションの依存関係をコピー
COPY package*.json ./
# 依存関係のインストール
RUN npm install
# アプリケーションのソースをコピー
COPY . .
EXPOSE 4000
CMD ["npm", "run", "dev"]
{
"name": "vba-formatter",
"version": "1.0.0",
"main": "src/index.js",
"scripts": {
"start": "node src/index.js",
"dev": "nodemon src/index.js"
},
"dependencies": {
"@aws-sdk/client-bedrock-runtime": "^3.0.0",
"dotenv": "^16.3.1",
"express": "^4.18.2",
"iconv-lite": "^0.6.3",
"mysql2": "^3.6.5",
"nodemon": "^3.0.2"
}
}
// src/db/connection.js
/**
* MySQL2のPromise版を使用
* Promise-basedなAPIでデータベース操作を行う
*/
const mysql = require('mysql2/promise');
/**
* データベース接続の修正履歴
*
* 1. 接続エラーの解消
* - 接続タイムアウトの設定追加
* - リトライロジックの実装
* - エラーハンドリングの強化
*
* 2. コネクションプールの最適化
* - プール設定の調整
* - 接続数の制限設定
*
* 3. デバッグ機能の強化
* - 詳細なログ出力の追加
* - 接続状態の監視機能
*/
/**
* データベース接続の文字コード処理の詳細説明
*
* 【文字化けが発生理由】
* データベースとアプリケーション間でデータをやり取りする際、
* 以下の3つのポイントで文字コードの変換が発生します:
*
* 1. アプリケーション → データベース(データ送信時)
* 2. データベース内でのデータ保存
* 3. データベース → アプリケーション(データ取得時)
*
* 【設定項目の説明】
* 1. charset: 'utf8mb4'
* - 接続時の文字コードを指定
* - データベースとの通信で使用する文字コードを決定
*
* 2. collation: 'utf8mb4_unicode_ci'
* - 文字の照合順序を指定
* - 「ci」は Case Insensitive(大文字小文字を区別しない)
*
* 3. initializationCommands
* - 接続確立直後に実行されるコマンド
* - セッションごとに文字コード設定を確実に行う
*
* 【改善の仕組み】
* - 接続時に文字コード設定を強制的に行う
* - セッションごとに設定を初期化
* - バイナリデータ経由で確実な文字コード変換を実現
*/
/**
* データベース接続設定の拡張
* - 環境変数から設定を読み込み、なければデフォルト値を使用
* - utf8mb4を使用して絵文字を含む多言語対応
*/
const config = {
host: process.env.DB_HOST || 'db',
user: process.env.DB_USER || 'vbauser',
password: process.env.DB_PASSWORD || 'vbapassword',
database: process.env.DB_NAME || 'vba_formatter',
charset: 'utf8mb4',
collation: 'utf8mb4_unicode_ci',
// 文字コード関連の設定を追加
connectionLimit: 10,
supportBigNumbers: true,
bigNumberStrings: true,
dateStrings: true,
// 明示的な文字コード設定
charset: 'utf8mb4',
// コネクション確立時の初期化コマンド
initializationCommands: [
'SET NAMES utf8mb4',
'SET CHARACTER SET utf8mb4',
'SET SESSION collation_connection = utf8mb4_unicode_ci'
]
};
// コネクションプールのインスタンス
let pool;
let connectionAttempts = 0;
const MAX_RETRIES = 5;
/**
* データベース接続を試行する関数
* リトライロジック付き
*/
const initPool = async () => {
while (connectionAttempts < MAX_RETRIES) {
try {
console.log(`データベース接続を試行中... (試行回数: ${connectionAttempts + 1})`);
// プールの作成
pool = mysql.createPool(config);
// 接続テスト
await pool.query('SELECT 1');
// 文字コード設定の確認
const [charsetResults] = await pool.query('SHOW VARIABLES LIKE "character%"');
console.log('データベース文字コード設定:', charsetResults);
console.log('データベース接続成功');
return pool;
} catch (error) {
connectionAttempts++;
console.error(`データベース接続エラー (試行回数: ${connectionAttempts}):`, error);
if (connectionAttempts >= MAX_RETRIES) {
throw new Error(`データベース接続に失敗しました。最大試行回数(${MAX_RETRIES})を超えました。`);
}
// 再試行前に待機
await new Promise(resolve => setTimeout(resolve, 2000 * connectionAttempts));
}
}
};
/**
* クエリ実行用の関数
* 接続エラー時の再接続を含む
*/
const query = async (...args) => {
try {
if (!pool) {
await initPool();
}
return await pool.query(...args);
} catch (error) {
console.error('クエリ実行エラー:', error);
// 接続エラーの場合は再接続を試みる
if (error.code === 'PROTOCOL_CONNECTION_LOST') {
pool = null;
return query(...args);
}
throw error;
}
};
/**
* データベース操作用の関数をエクスポート
* - query: SQL実行用の関数
* - getPool: プールインスタンス取得用の関数
*/
module.exports = {
// クエリ実行関数
query,
// プール取得関数
getPool: async () => {
if (!pool) {
await initPool(); // プールが未初期化なら初期化
}
return pool;
}
};
/**
* 文字コード設定の詳細説明
*
* 【文字化け発生の背景】
* データベースでは文字データを保存する際に「文字コード」という形式を使用します。
* 日本語などの多言語文字を正しく扱うにはUTF-8mb4という文字コードが必要です。
*
* 【各設定の役割】
* 1. SET NAMES utf8mb4
* - クライアントとサーバー間の通信で使用する文字コードを設定
* - データの送受信時の文字化けを防ぐ
*
* 2. SET CHARACTER SET utf8mb4
* - データベースが使用する文字コードを設定
* - データ保存時の文字化けを防ぐ
*
* 3. COLLATE utf8mb4_unicode_ci
* - 文字の照合順序(ソート順)を設定
* - 日本語を含む多言語での正しい並び順を保証
*
* 【utf8mb4を使用する理由】
* - 絵文字を含むすべてのUnicode文字を扱える
* - 従来のutf8より広い文字範囲をサポート
* - 将来的な文字コードの拡張にも対応可能
*/
-- データベースの文字コード設定を確実に行う
SET NAMES utf8mb4;
SET CHARACTER SET utf8mb4;
-- データベースの文字コードを強制的にUTF8mb4に設定
-- これによりデータベースレベルで日本語を正しく扱える
ALTER DATABASE vba_formatter CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- カラム
CREATE TABLE IF NOT EXISTS prompts (
id VARCHAR(50) PRIMARY KEY,
name VARCHAR(100) NOT NULL,
content TEXT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB
CHARACTER SET utf8mb4
COLLATE utf8mb4_unicode_ci;
/**
* プロンプトの表示順序の制御について
*
* 【表示順序の制御方法】
* 1. nameカラムに接頭辞を付けて制御
* - 数字の場合はゼロ埋めして2桁で統一
* - 例:'01_', '02_' など
*
* 2. 意図した順序:
* - "01_VBAプロンプト1" (最初に表示)
* - "02_VBAプロンプト2" (2番目に表示)
* - "03_HTML BEMの命名規則" (3番目に表示)
* - "04_開発テスト用" (4番目に表示)
*/
-- 初期データの挿入(順序を制御するため、nameを修正)
INSERT INTO prompts (id, name, content) VALUES
('prompt01', '01_VBAプロンプト1',
'# ここからがAIへの指示内容です
=============================================
# AIの役割定義
あなたはVBAコードの専門家として、以下のコードを分析し、優先順位に従って改善してください。
# 入力コード部分
```vba
{code}
```
# 改善要件(優先順位:高 - 第一弾)
=============================================
1. インデント処理
- SUBからENDSUBまでTABひとつ分のインデント
- IF文、With文、For文等の制御構造にもインデント
- ネストレベルに応じて適切なインデント
2. ヘッダー・フッター追加
- Sub開始時のヘッダー:
Option Explicit
''***************************************************
'' [プロシージャ名]
''***************************************************
''【機 能】[機能の説明]
''【引 数】[引数の説明]
''【戻 り 値】[戻り値の説明]
''【機能説明】[詳細な説明]
''【備 考】[その他特記事項]
'' Copyright(c) 2024 Your Company All Rights Reserved.
''***************************************************
- 処理の区切り:
''***************************************************
'' 変数宣言
''***************************************************
[変数宣言部分]
- Sub終了時のフッター:
''***************************************************
''---------------------------------------------------
'' Version
''---------------------------------------------------
'' 1.00 | 2024.01.01 | ********* | *********
''***************************************************
# 改善要件(優先順位:中 - 第二弾)
=============================================
3. 変数名の改善
- 一文字の変数を禁止
- セル参照の変数は意味のある名前に変更
(例: Last_Row, Last_Column)
- 配列はArrayを付ける
- その他の変数はDataを付ける
4. Public変数・Call文の処理
- Public変数にはPublic関数であることをコメントで記載
- Call文にはモジュール名を追加
(例: Call Module1.印刷)
5. 変数宣言のコメント
- 宣言した変数の横にコメントを追加
- 使用目的や内容を簡潔に説明
# 改善要件(優先順位:中 - 第三弾)
=============================================
6. 変数・シート名の処理
- 日本語変数名を英語に変更
- 未宣言変数を「変数宣言」セクションに追加
- 型判定できない変数はVariantに
- シート名は定数(Const)で定義
- シート追加時の名前はVariantで処理
7. コメント追加
- IF文、With文などの制御構造にコメント
- 配列の内容説明
- SET文の説明
- Offsetのコメント必須
(参照セルと目的を明記)
8. コードの最適化
- 類似コードが3回以上続く場合はループ化
- パスの直書きは避け、pathに置換
(元のパスはコメントとして保持)
# 出力形式
=============================================
優先順位の高い要件(第一弾)から順に適用し、
改善したVBAコードのみを出力してください。'),
('prompt02', '02_VBAプロンプト2',
'# ここからがAIへの指示内容です(コードレビュー版)
=============================================
# AIの役割定義
あなたはシニアVBA開発者として、以下のコードをレビューし、ベストプラクティスに基づいて改善してください。
# 入力コード部分
```vba
{code}
```
# レビュー観点
=============================================
1. コーディング規約準拠
2. バグの可能性
3. パフォーマンスボトルネック
4. セキュリティリスク
# 出力形式
=============================================
コードレビューコメントと改善後のコードを提供してください。'),
('prompt03', '03_HTML BEMの命名規則',
'# ここからがAIへの指示内容です(HTML BEM分析版)
=============================================
# AIの役割定義
あなたはHTMLとCSSの専門家として、クラス名をBEM命名規則に基づいて改善します。
# 入力コード部分
```html
{code}
```
# 改善要件
=============================================
1. BEMの基本ルール適用
- Block: 独立したコンポーネント(例: header, menu)
- Element: Blockの一部(例: menu__item)
- Modifier: 状態や見た目の変更(例: menu__item--active)
2. 命名規則の統一
- Blockは意味のある名前を使用
- Element は __ (アンダースコア2つ) で接続
- Modifier は -- (ハイフン2つ) で接続
- 全て小文字、ハイフンで単語を区切る
3. クラス名の階層構造
- 最大2階層までの要素の入れ子
- Block内のElement同士の依存関係を避ける
- 共通の機能はMixinとして抽出
4. コンポーネントの分割
- 再利用可能なBlockの特定
- 共通のModifierパターンの抽出
- コンポーネント間の依存関係の最小化
5. レスポンシブ対応
- Modifierでのブレイクポイント管理
- コンテナクエリの活用
- フレックスボックス/グリッドの適切な使用
# 出力形式
=============================================
1. 改善後のHTML
2. 各クラス名の説明とBEMルールとの対応
3. コンポーネント構造の解説'),
('prompt04', '04_開発テスト用',
'# ここからがAIへの指示内容です
=============================================
# 以下のコードをレビューし、改善要件に基づいて修正してください。
-
# 入力コード部分
```
{code}
```
# 改善要件
=============================================
1. xx
- xx
- xx
- xx
2. xx
- xx
- xx
- xx
3. xx
- xx
- xx
- xx
# 出力形式
=============================================
1. xx
2. xx
3. xx')
ON DUPLICATE KEY UPDATE
name = VALUES(name),
content = VALUES(content);
// ========================================
// サーバーのメインファイル (index.js)
// VBAフォーマッターのバックエンド処理を担当
// ========================================
// 必要なモジュールのインポート
const path = require("path"); // pathモジュールを追加
const express = require("express");
const {analyzeCode} = require("./bedrock/analyzer"); // Bedrock AIの分析機能
// const { formatVBA } = require('./formatter'); // 基本的なフォーマット、現在機能使用していない
const db = require("./db/connection"); // データベース接続を追加
// Expressアプリケーションの初期化
const app = express();
// ミドルウェアの設定
// 静的ファイルのパスを修正- Docker環境での絶対パス指定に変更
app.use(express.static(path.join(__dirname, "public")));
// JSONリクエストの解析を有効化
app.use(express.json());
// CORSの設定を追加
app.use((req, res, next) => {
res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Methods", "GET, PUT, POST, DELETE");
res.header("Access-Control-Allow-Headers", "Content-Type");
next();
});
// Content-Typeの設定(APIエンドポイントのみに適用)
app.use((req, res, next) => {
if (
req.path.startsWith("/api") ||
req.path.startsWith("/prompts") ||
req.path === "/format" ||
req.path === "/prompt-list"
) {
res.setHeader("Content-Type", "application/json; charset=utf-8");
// 追加: レスポンスエンコーディングを設定
res.setHeader("Transfer-Encoding", "chunked");
}
next();
});
/**
* プロンプト一覧取得APIをDB対応に修正
* - MySQLからデータを取得するように変更
*/
app.get("/prompt-list", async (req, res) => {
try {
const [rows] = await db.query("SELECT id, name, description FROM prompts");
res.json({
success: true,
prompts: rows,
});
} catch (error) {
console.error("データベースエラー:", error);
res.status(500).json({
success: false,
error: "プロンプト一覧の取得に失敗しました",
});
}
});
app.get("/about", (req, res) => {
res.sendFile(path.join(__dirname, "public", "about.html"));
});
/**
* 文字化け解消のための修正履歴 2024-03-xx
*
* 1. Content-Typeヘッダーの設定を試行
* res.setHeader('Content-Type', 'application/json; charset=utf-8');
* → 部分的な効果あり、完全な解決には至らず
*
* 2. Buffer経由のデコードを試行
* Buffer.from(rows[0].content).toString('utf8')
* → 一部の文字で文字化けが発生
*
* 3. クエリでのCONVERT使用を試行
* SELECT CONVERT(content USING utf8mb4) as content
* SELECT CONVERT(CAST(content AS BINARY) USING utf8) as content
* → 特定の文字で文字化けが継続
*
* 4. HEX形式での取得を試行
* SELECT HEX(content) as content_hex FROM prompts
* → デコード時に問題発生
*
* 5. クエリオプションでのエンコーディング指定を試行
* {sql: "SELECT * FROM prompts", encoding: 'utf8mb4'}
* → mysql2では未サポート
*
* 6. セッション文字コードの設定とバイナリ変換を試行
* SET NAMES utf8mb4 +
* SELECT CAST(CONVERT(content USING binary) AS CHAR CHARACTER SET utf8mb4)
* → 部分的な改善
*
* 7. iconv-liteによる文字コード変換を試行
* const iconv = require('iconv-lite');
* iconv.decode(Buffer.from(content), 'utf8');
* → 特定のケースで例外発生
*
* 8. execute()による実行を試行
* → db.executeは存在しない
* → プールインスタンスにはexecuteメソッドがない
*
* 9. コネクション管理の改善を試行
* - getConnection()でコネクションを取得
* - 取得したコネクションでexecuteを実行
* - 処理後にコネクションを解放
* → コネクション管理は改善したが文字化けは解消せず
*
* 10. 現在の解決策(2024-03-xx):
* - データベースレベルでの文字コード設定の確実な適用
* - 接続時の初期化コマンドによる文字コード設定
* - バイナリ経由での確実な文字コード変換
* - レスポンスヘッダーとエンコーディングの最適化
* → 以下のアプローチの組み合わせで解決
* 1. データベース接続時の文字コード初期化
* 2. バイナリ経由での文字コード変換
* 3. Buffer処理による確実なエンコーディング
* 4. クライアントでのデコード処理の最適化
*/
/**
* APIエンドポイントでの文字コード処理の詳細説明
*
* 【文字化け解消の3段階】
* 1. データベースからの取得時
* - バイナリデータとして一旦取得
* - UTF-8mb4として再解釈
*
* 2. JSONレスポンス生成時
* - Buffer経由で文字コードを確実に変換
* - 文字化けしやすい文字も正しく処理
*
* 3. クライアントへの送信時
* - Content-Typeヘッダーで文字コードを明示
* - Transfer-Encodingの設定で大きなデータも安全に送信
*
* 【なぜこの方法で解決できたのか】
* 1. バイナリ経由の変換
* - 文字コードの解釈を一旦リセット
* - 確実にUTF-8として再解釈
*
* 2. 多層的なアプローチ
* - DB設定
* - 接続設定
* - アプリケーション処理
* 全ての層で文字コードを適切に処理
*
* 3. ヘッダー設定の最適化
* - クライアントに文字コードを正しく伝達
* - ブラウザでの解釈を確実に制御
*/
// プロンプト一覧を取得するエンドポイント
app.get("/api/prompt-info", async (req, res) => {
try {
// 文字コードの明示的な設定
await db.query("SET NAMES utf8mb4");
await db.query("SET CHARACTER SET utf8mb4");
await db.query("SET SESSION collation_connection = utf8mb4_unicode_ci");
// バイナリ経由での文字列取得(最新の解決策)
const [rows] = await db.query(`
SELECT
id,
CONVERT(CAST(CONVERT(name USING binary) AS BINARY) USING utf8mb4) as name,
CONVERT(CAST(CONVERT(content USING binary) AS BINARY) USING utf8mb4) as content
FROM prompts
ORDER BY name
`);
// レスポンスヘッダーの設定
res.setHeader('Content-Type', 'application/json; charset=utf8');
res.setHeader('Transfer-Encoding', 'chunked');
// Buffer経由でのエンコーディング
const jsonString = JSON.stringify({
success: true,
prompts: rows.map(row => ({
id: row.id,
name: Buffer.from(row.name).toString('utf8'),
content: Buffer.from(row.content).toString('utf8')
}))
});
res.end(Buffer.from(jsonString, 'utf8'));
} catch (error) {
console.error("データ取得エラー:", error);
res.status(500).json({
success: false,
error: "データの取得に失敗しました"
});
}
});
// 個別のプロンプト内容を取得するエンドポイント
app.get("/api/prompts/:id", async (req, res) => {
// レスポンスヘッダーを設定
res.setHeader('Content-Type', 'application/json; charset=utf-8');
try {
// UTF-8エンコーディングを明示的に指定してコンテンツを取得
const [rows] = await db.query(
"SELECT CAST(content AS CHAR CHARACTER SET utf8mb4) as content FROM prompts WHERE id = ?",
[req.params.id]
);
if (!rows || rows.length === 0) {
return res.status(404).json({
success: false,
error: "指定されたプロンプトが見つかりません"
});
}
// 成功時のレスポンス形式を統一
res.json({
success: true,
content: rows[0].content
});
} catch (error) {
// エラーログの出力
console.error("プロンプト取得エラー:", error);
// エラー時のレスポンス形式を統一
res.status(500).json({
success: false,
error: "プロンプトの取得に失敗しました",
details: process.env.NODE_ENV === 'development' ? error.message : undefined
});
}
});
/**
* フォーマットAPIにプロンプトIDの処理を追加
* - リクエストからプロンプトIDを取得
* - DBから対応するプロンプトを取得して使用
*/
app.post("/format", async (req, res) => {
try {
const {code, promptId} = req.body;
const [rows] = await db.query(
"SELECT CAST(content AS CHAR CHARACTER SET utf8mb4) as content FROM prompts WHERE id = ?",
[promptId]
);
if (!rows || rows.length === 0) {
return res.status(404).json({
success: false,
error: "プロンプトが見つかりません",
});
}
const formattedCode = await analyzeCode(code, rows[0].content);
res.json({success: true, formatted: formattedCode});
} catch (error) {
console.error("Format error:", error);
res.status(500).json({
success: false,
error: error.message || "フォーマット処理に失敗しました",
});
}
});
// プロンプト更新API
app.put("/prompts/:id", async (req, res) => {
console.log("Update request received:", {
params: req.params,
body: req.body,
});
try {
const {id} = req.params;
const {content} = req.body;
// データベース接続テスト
const [testConnection] = await db.query("SELECT 1");
console.log("Database connection test:", testConnection);
const [result] = await db.query(
"UPDATE prompts SET content = ? WHERE id = ?",
[content, id]
);
console.log("Update result:", result);
if (result.affectedRows === 0) {
return res.status(404).json({
success: false,
error: "プロンプトが見つかりません",
});
}
res.json({
success: true,
message: "更新しました",
});
} catch (error) {
console.error("Database error:", error);
res.status(500).json({
success: false,
error: error.message,
});
}
});
// サーバー起動
const PORT = process.env.PORT || 4000;
app.listen(PORT, "0.0.0.0", () => {
console.log("========================================");
console.log(`Server running on http://localhost:${PORT}`);
// 登録されているルートを表示
app._router.stack.forEach((r) => {
if (r.route && r.route.path) {
console.log(`Route: ${r.route.path}`);
console.log(`Methods:`, r.route.methods);
}
});
console.log("========================================");
});
.envファイルで要環境変数設定
// AWS SDKからBedrockのクライアントとコマンドをインポート
const { InvokeModelCommand } = require("@aws-sdk/client-bedrock-runtime");
const client = require('./client');
/**
* コードをClaudeモデルで分析する関数
* @param {string} code - 分析対象のコード(VBAまたはHTML)
* @param {string} promptContent - 使用するプロンプトの内容
* @returns {Promise<string>} - 改善されたコード
*/
async function analyzeCode(code, promptContent) {
try {
// 入力検証
if (!code || !promptContent) {
throw new Error('コードとプロンプトの内容は必須です');
}
// プロンプト内容にコードを挿入
// {code}をユーザー入力のコードに置換
const fullPrompt = promptContent.replace('{code}', code);
// Claude用のリクエストコマンドを作成
const command = new InvokeModelCommand({
// Claude 3 Sonnetモデルを指定(最新バージョン)
modelId: "anthropic.claude-3-sonnet-20240229-v1:0",
contentType: "application/json",
accept: "application/json",
// Bedrock API用のリクエストボディを構築
body: JSON.stringify({
// Bedrockのバージョンを指定
anthropic_version: "bedrock-2023-05-31",
// 最大トークン数(出力の長さ制限)
max_tokens: 2048,
// メッセージ形式でプロンプトを送信
messages: [
{
role: "user",
content: fullPrompt
}
],
// 必要に応じて追加のパラメータを設定可能
// temperature: 0.7, // 生成の多様性(0-1)
// top_p: 0.9, // 出力のランダム性
})
});
// BedrockAPIを呼び出し
const response = await client.send(command);
// レスポンスの処理(バイナリからUTF-8文字列に変換)
const responseBody = Buffer.from(response.body).toString('utf8');
const jsonResponse = JSON.parse(responseBody);
// Claudeのレスポンス形式から結果を抽出
// content[0].textに実際の生成テキストが含まれる
if (jsonResponse.content && jsonResponse.content[0]) {
return jsonResponse.content[0].text;
}
throw new Error('AIモデルからの応答が不正な形式です');
} catch (error) {
// エラーログを出力し、上位層に伝播
console.error("コード分析エラー:", error);
console.error("エラー詳細:", {
message: error.message,
code: error.code,
requestId: error.$metadata?.requestId
});
throw error;
}
}
// analyzeCode関数をエクスポート
module.exports = {
analyzeCode
};
require('dotenv').config();
const { BedrockRuntimeClient } = require("@aws-sdk/client-bedrock-runtime");
// Bedrockクライアントの設定
const client = new BedrockRuntimeClient({
region: process.env.AWS_REGION || "us-east-1",
credentials: {
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY
}
});
module.exports = client;
<!DOCTYPE html>
<html lang="ja">
<head>
<!-- 文字エンコーディングとビューポートの設定 -->
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>VBA Formatter</title>
<!-- reset.css ress -->
<link rel="stylesheet" href="https://unpkg.com/ress/dist/ress.min.css" />
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="style.css">
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+JP:wght@100..900&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css">
</head>
<body>
<!-- メインコンテナ -->
<div class="container">
<h1>VBA Formatter</h1>
<div class="nav-links">
<a href="index.html">ホーム</a>
<a href="about.html">About</a>
</div>
<!-- AIへの指示内容表示セクション(src/bedrock/prompts.jsの内容) -->
<div class="prompt-section">
<h2>AIへの指示内容</h2>
<div class="prompt-selector">
<select id="promptSelect" onchange="changePrompt()">
<option value="">読み込み中...</option>
</select>
<button id="editButton" onclick="toggleEdit()" class="edit-button">
編集
</button>
</div>
<!-- プロンプト内容を動的に表示する要素 -->
<div class="prompt-display-container">
<div class="user-icon"></div>
<div class="prompt-display-wrapper">
<pre id="promptDisplay" class="prompt-display" contenteditable="false">ユーザーからの指示内容がここに表示されます</pre>
</div>
</div>
<button id="saveButton" onclick="savePrompt()" class="save-button" style="display: none;">
保存
</button>
</div>
<!-- コードエディタ部分 -->
<div class="editors">
<!-- 入力用エディタ -->
<div class="editor">
<h2>分析するコード</h2>
<!-- 入力用テキストエリア:ユーザーがコードを入力または貼り付け可能 -->
<textarea id="input" placeholder="コードを入力してください"></textarea>
<!-- ファイル選択ボタン:.basと.txtファイルのみ受付 -->
<input type="file" id="vbaFile" accept=".bas,.txt,.html,.css,.js">
</div>
<!-- 出力用エディタ -->
<div class="editor">
<h2>改善後のコード</h2>
<!-- 出力用テキストエリア:読み取り専用で改善されたコードを表示 -->
<textarea id="output" readonly></textarea>
<!-- フォーマット実行ボタン -->
<button onclick="formatCode()" id="formatButton">Format</button>
<!-- ポップアップメッセージ表示エリア -->
<div id="popup" class="popup">クリップボードにコピーしました</div>
</div>
</div>
</div>
<script src="./script.js"></script>
</body>
</html>
/**
* ページ初期ロード時の処理
*
* 【プロンプト選択の仕組み】
* 1. ページロード時にプロンプト一覧を取得
* 2. セレクトボックスの最初のオプションが自動的に選択される
* 3. changePrompt()が呼ばれ、選択されたプロンプトの内容を表示
*
* 【選択の優先順位】
* 1. セレクトボックスの最初のオプション(データベースのORDER BY name順で最初のプロンプト)
* 2. エラー時は「プロンプトの読み込みに失敗しました」というメッセージを表示
*/
window.onload = async function() {
try {
// プロンプトリストの取得
const response = await fetch('/api/prompt-info');
const buffer = await response.arrayBuffer();
const decoder = new TextDecoder('utf-8');
const jsonString = decoder.decode(buffer);
const data = JSON.parse(jsonString);
if (data.success) {
// セレクトボックスの生成
// ORDER BY name でソートされたプロンプトリストの最初のものが自動選択される
const select = document.getElementById('promptSelect');
select.innerHTML = data.prompts.map(prompt =>
`<option value="${prompt.id}">${prompt.name}</option>`
).join('');
// 最初のプロンプトの内容を取得して表示
await changePrompt();
}
} catch (error) {
console.error('初期化エラー:', error);
document.querySelector('.prompt-display').textContent = 'プロンプトの読み込みに失敗しました';
}
};
// プロンプト切り替え関数
async function changePrompt() {
try {
const selectedPrompt = document.getElementById('promptSelect').value;
console.log('Selected prompt ID:', selectedPrompt); // デバッグログ
const response = await fetch(`/api/prompts/${selectedPrompt}`);
const data = await response.json();
console.log('API Response:', data); // デバッグログ
const promptDisplay = document.querySelector('.prompt-display');
if (data.success) {
promptDisplay.textContent = data.content;
} else {
promptDisplay.textContent = 'プロンプトの読み込みに失敗しました';
}
} catch (error) {
console.error('プロンプト取得エラー:', error);
document.querySelector('.prompt-display').textContent = 'プロンプトの読み込みに失敗しました';
}
}
/**
* コードのフォーマットを実行する関数
* 1. 入力を取得
* 2. サーバーにリクエスト
* 3. 結果を表示
*/
async function formatCode() {
const input = document.getElementById('input').value;
const promptId = document.getElementById('promptSelect').value;
const formatButton = document.getElementById('formatButton');
// デバッグログ追加
console.log('Formatting with:', {
promptId,
code: input
});
if (!input) {
alert('コードを入力してください');
return;
}
formatButton.innerHTML = '<span class="processing"><span></span><span></span><span></span></span>';
try {
const response = await fetch('/format', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
code: input,
promptId: promptId
})
});
// デバッグログ追加
console.log('Response status:', response.status);
const data = await response.json();
console.log('API Response:', data);
if (data.success) {
document.getElementById('output').value = data.formatted;
formatButton.innerHTML = '分析完了! クリックしてコピー<i class="fa-regular fa-copy fa-beat-fade fa-lg" style="vertical-align: 0;"></i>';
formatButton.onclick = copyToClipboard;
} else {
formatButton.textContent = 'エラー: ' + data.error;
}
} catch (error) {
console.error('フォーマットエラー:', error);
formatButton.textContent = 'エラー: ' + error.message;
}
formatButton.classList.remove('processing');
}
/**
* クリップボードにコピーする関数
* 出力用テキストエリアの内容をクリップボードにコピー
*/
function copyToClipboard() {
const outputTextarea = document.getElementById('output');
outputTextarea.select();
document.execCommand('copy');
// ポップアップメッセージを表示
const popup = document.getElementById('popup');
popup.classList.add('show');
// 一定時間後にポップアップメッセージを非表示にする
setTimeout(() => {
popup.classList.remove('show');
}, 2000); // 2秒後に非表示
}
/**
* ファイル選択時の処理
* 選択されたファイルの内容を入力エリアに表示
*/
document.getElementById('vbaFile').addEventListener('change', (e) => {
// 選択されたファイルを取得
const file = e.target.files[0];
if (file) {
// FileReaderを使用してファイルの内容を読み込み
const reader = new FileReader();
// ファイル読み込み完了時の処理
reader.onload = (e) => {
// 読み込んだ内容を入力エリアに設定
document.getElementById('input').value = e.target.result;
};
// ファイルをテキストとして読み込み開始
reader.readAsText(file);
}
});
// プロンプトの保存
async function savePrompt() {
try {
const promptId = document.getElementById('promptSelect').value;
const content = document.getElementById('promptDisplay').textContent;
console.log('Saving prompt:', promptId);
console.log('Content:', content);
const saveButton = document.getElementById('saveButton');
saveButton.textContent = '保存中...';
saveButton.disabled = true;
// URLを確認のため出力
const url = `/prompts/${promptId}`;
console.log('Request URL:', url);
const response = await fetch(url, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
content: content
})
});
console.log('Response status:', response.status);
const data = await response.json();
console.log('Response data:', data);
if (data.success) {
alert('保存しました');
document.getElementById('editButton').click();
} else {
alert('保存に失敗しました: ' + data.error);
}
} catch (error) {
console.error('保存エラー:', error);
alert('エラーが発生しました: ' + error.message);
} finally {
const saveButton = document.getElementById('saveButton');
saveButton.textContent = '保存';
saveButton.disabled = false;
}
}
// 編集モードの切り替え
function toggleEdit() {
const promptDisplay = document.getElementById('promptDisplay');
const editButton = document.getElementById('editButton');
const saveButton = document.getElementById('saveButton');
const isEditing = promptDisplay.contentEditable === 'true';
if (isEditing) {
// 編集モード終了
promptDisplay.contentEditable = 'false';
editButton.textContent = '編集';
saveButton.style.display = 'none';
promptDisplay.classList.remove('editing');
} else {
// 編集モード開始
promptDisplay.contentEditable = 'true';
editButton.textContent = 'キャンセル';
saveButton.style.display = 'block';
promptDisplay.classList.add('editing');
// カーソルを末尾に設定
const range = document.createRange();
const sel = window.getSelection();
range.selectNodeContents(promptDisplay);
range.collapse(false);
sel.removeAllRanges();
sel.addRange(range);
promptDisplay.focus();
}
}
// データ取得時の文字コード処理
async function fetchPromptInfo() {
try {
const response = await fetch('/api/prompt-info');
const buffer = await response.arrayBuffer();
const decoder = new TextDecoder('utf-8');
const jsonString = decoder.decode(buffer);
return JSON.parse(jsonString);
} catch (error) {
console.error('データ取得エラー:', error);
throw error;
}
}
body {
font-family: "Noto Sans JP", serif;
background-color: #f1f2f3;
}
/* メインコンテナ */
.container {
max-width: 1200px;
margin: 0 auto;
padding: 0 2rem 2rem;
}
h1 {
background-color: #fff;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
text-align: center;
letter-spacing: 0.1em;
padding: 1rem 0;
margin: 0 calc(50% - 50vw);
width: 100vw;
margin-bottom: 2rem;
}
h2 {
margin-bottom: 1rem;
text-align: center;
letter-spacing: 0.1em;
}
.nav-links {
text-align: center;
margin-bottom: 2rem;
}
.nav-links a {
display: inline-block;
padding: 0.5rem 1rem;
background-color: #e9be3b;
color: white;
text-decoration: none;
border-radius: 4px;
margin: 0 0.5rem;
}
.nav-links a:hover {
background-color: #d3b458;
}
/* AIへの指示内容表示部分 */
.prompt-section {
margin-bottom: 2rem;
}
.prompt-selector {
display: flex;
justify-content: center;
gap: 1rem;
margin-bottom: 1rem;
}
.edit-button {
background-color: #4a90e2;
height: 2.5rem;
}
.edit-button:hover {
background-color: #357abd;
}
.save-button {
display: none;
margin: 1rem auto;
background-color: #4caf50;
width: 200px;
}
.save-button:hover {
background-color: #45a049;
}
.prompt-display[contenteditable="true"] {
border: 2px solid #4a90e2;
padding: 1rem;
outline: none;
}
.prompt-selector select {
padding: 0.5rem 2rem 0.5rem 1rem;
border: 1px solid #ccc;
border-radius: 4px;
background-color: white;
font-size: 1rem;
cursor: pointer;
appearance: none;
background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3e%3cpolyline points='6 9 12 15 18 9'%3e%3c/polyline%3e%3c/svg%3e");
background-repeat: no-repeat;
background-position: right 0.7rem center;
background-size: 1em;
}
.prompt-selector select:hover {
border-color: #888;
}
.prompt-selector select:focus {
outline: none;
border-color: #e9be3b;
box-shadow: 0 0 0 2px rgba(233, 190, 59, 0.2);
}
.prompt-display-container {
display: flex;
align-items: end;
justify-content: center;
gap: 2rem;
margin: 0 auto;
}
.user-icon {
width: 40px;
height: 40px;
background-color: #f6ce55;
border-radius: 50%;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
margin-bottom: 3rem;
position: relative;
}
.user-icon::after {
content: "";
position: absolute;
top: 96%;
left: 50%;
transform: translateX(-50%);
width: 50px;
height: 40px;
background-color: #f6ce55;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
border-top-left-radius: 50%;
border-top-right-radius: 50%;
}
.prompt-display-wrapper {
max-width: 80%;
display: flex;
align-items: center;
justify-content: center;
background-color: #fff;
border-radius: 5rem 5rem 5rem 0;
box-shadow: 2px 2px 4px rgba(0, 0, 0, 0.1);
padding: 2rem;
}
.prompt-display {
font-family: monospace;
font-size: 0.825rem;
line-height: 1.5;
white-space: pre-wrap;
max-height: 400px;
min-height: 300px;
min-width: 600px;
height: auto;
overflow-y: auto;
resize: vertical;
padding: 1rem;
}
/* カスタムスクロールバーのスタイル */
.prompt-display::-webkit-scrollbar {
width: 0.5rem; /* スクロールバーの幅 */
}
.prompt-display::-webkit-scrollbar-thumb {
background: #888;
border-radius: 1rem;
}
/* エディタ部分 */
.editors {
display: flex;
gap: 2rem;
margin-bottom: 1rem;
position: relative;
}
.editor {
flex: 1;
display: flex;
flex-direction: column;
}
.editor > * {
margin-bottom: 1rem;
}
.editor > *:last-child {
margin-top: auto !important;
}
.editor input {
height: 3rem;
background-color: #ccc;
padding: 0.5rem 1rem;
border-radius: 4px;
width: 100%;
transition: all 0.3s ease;
}
.editor input:hover {
background-color: #bbb;
}
.editor .detail {
margin-top: 0.5rem;
color: #ff565d;
}
/* テキストエリア */
.editor textarea {
width: 100%;
height: 500px;
font-family: monospace;
padding: 10px;
font-size: 14px;
line-height: 1.5;
border: 2px solid #ccc;
background-color: #fff;
border-radius: 4px;
}
button {
height: 3rem;
padding: 0.5rem 1rem;
background-color: #e9be3b;
border-radius: 4px;
color: white;
letter-spacing: 0.1em;
border: none;
cursor: pointer;
position: relative;
}
button:hover {
background-color: #d3b458;
transition: all 0.3s ease;
}
/* アニメーションを追加するクラス */
.processing {
display: inline-flex;
gap: 0.5rem;
}
.processing span {
display: inline-block;
width: 0.75rem;
height: 0.75rem;
background-color: #fff;
border-radius: 50%;
animation: fadeDots 1.5s infinite ease-in-out;
}
/* それぞれのドットに異なるアニメーションの遅延を設定 */
.processing span:nth-child(1) {
animation-delay: 0s;
}
.processing span:nth-child(2) {
animation-delay: 0.2s;
}
.processing span:nth-child(3) {
animation-delay: 0.4s;
}
/* 薄さ(透明度)を変化させるアニメーション */
@keyframes fadeDots {
0%,
100% {
opacity: 0.2;
} /* 薄くなる */
50% {
opacity: 1;
} /* 濃くなる */
}
.popup {
visibility: hidden; /* 初期状態では非表示 */
position: absolute;
bottom: 4.5rem;
right: 0.5rem;
background-color: rgba(76, 175, 80, 0.8);
color: white;
padding: 0.5rem 1rem;
border-radius: 4px;
color: white;
letter-spacing: 0.1em;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
z-index: 1000;
opacity: 0;
transform: translateY(1rem);
transition: opacity 0.5s ease, transform 0.5s ease, visibility 0.5s;
}
.popup.show {
visibility: visible;
opacity: 1;
transform: translateY(0);
}
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>About VBA Formatter</title>
<!-- <link rel="stylesheet" href="https://unpkg.com/ress/dist/ress.min.css" /> -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+JP:wght@100..900&display=swap" rel="stylesheet">
<link rel="stylesheet" href="style.css">
<style>
.section {
background: white;
border-radius: 8px;
padding: 2rem;
margin-bottom: 2rem;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.prompt-card {
border: solid 2px #ccc;
border-radius: 8px;
padding: 2rem;
margin-bottom: 2rem;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.prompt-content {
background: #f8f9fa;
padding: 1.5rem;
border-radius: 4px;
white-space: pre-wrap;
font-family: monospace;
font-size: 0.825rem;
line-height: 1.5;
overflow-x: auto;
}
.prompt-footer {
margin-top: 1rem;
padding-top: 1rem;
border-top: 1px solid #eee;
color: #666;
font-size: 0.9rem;
}
.error {
color: #dc3545;
padding: 1rem;
background: #fff;
border-left: 4px solid #dc3545;
margin: 1rem 0;
}
</style>
</head>
<body>
<h1>VBA Formatter について</h1>
<div class="container">
<div class="nav-links">
<a href="index.html">ホーム</a>
<a href="about.html">About</a>
</div>
<!-- プロンプトセクション -->
<div class="section">
<h2>プロンプト管理</h2>
<div id="promptInfo" class="prompt-info">
<!-- プロンプト情報がここに動的に挿入されます -->
<div class="loading">読み込み中...</div>
</div>
</div>
<div class="section">
<h2>システム概要</h2>
<p>
Code Formatterは、コードを自動的に整形し、プロンプトに従ってコードを改善するツールです。
<br>Docker環境で動作し、Node.jsとMySQLを利用しています。
</p>
</div>
<div class="section">
<h2>Docker環境について</h2>
<ul class="point-list">
<li>コンテナの停止(docker-compose down): データは保持されます</li>
<li>Docker Desktop停止: データは保持されます</li>
<li>ボリューム削除(docker-compose down --volumes): データは消失します</li>
</ul>
</div>
<div class="section">
<h2>使用方法</h2>
<ul class="point-list">
<li>プロンプトの選択: ドロップダウンメニューから選択</li>
<li>編集: 「編集」ボタンをクリックして内容を修正</li>
<li>保存: 「保存」ボタンをクリックしてデータベースに保存</li>
<li>確認: 保存完了のメッセージを確認</li>
</ul>
</div>
<div class="section">
<h2>開発者向け情報</h2>
<ul class="point-list">
<li>デバッグ: docker-compose logsでログを確認できます</li>
<li>データベース確認:<br>
- docker exec -it 1be476fccbd035b76a53810a40486a0a427266c50deb5b681671dcc2557ca5be bashで接続<br>
- mysql -u root -p</li>
<li>ボリューム確認: docker volume inspectで詳細を確認</li>
</ul>
</div>
</div>
<script>
// プロンプトの表示用スクリプト
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
function formatDate(dateStr) {
return new Date(dateStr).toLocaleString('ja-JP', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit'
});
}
async function loadPromptInfo() {
try {
const response = await fetch('/api/prompt-info');
const data = await response.json();
if (data.success) {
const promptsHtml = data.prompts.map(prompt => `
<div class="prompt-card">
<h3>${escapeHtml(prompt.name)}</h3>
<p class="description">${escapeHtml(prompt.description)}</p>
<pre class="prompt-content">${escapeHtml(prompt.content)}</pre>
<div class="prompt-footer">
最終更新: ${formatDate(prompt.updated_at)}
</div>
</div>
`).join('');
document.getElementById('promptInfo').innerHTML = promptsHtml;
}
} catch (error) {
console.error('プロンプト情報の取得に失敗:', error);
document.getElementById('promptInfo').innerHTML =
'<div class="error">プロンプト情報の取得に失敗しました</div>';
}
}
document.addEventListener('DOMContentLoaded', loadPromptInfo);
</script>
</body>
</html>
docker-compose.yml
connection.js
index.js
package.json
そして、先ほどのnpm install
については、Dockerコンテナ内で実行する場合は:
試したこと:
DBの文字コード設定確認
utf8mb4
であることを確認utf8mb4_unicode_ci
であることを確認connection.js
での試み
index.js
での試み
データ確認
文字コードとは:
E38182
として表現多重エンコードとは:
E38182
(1回目の変換:正常)E38182
→ C3A3E28182
(2回目の変換:不要)現在の問題:
試したアプローチ:
未試行と考えられるアプローチ:
ssh ユーザー名@IPアドレス
SSH接続のポートフォワーディングとは、SSHトンネルを利用して、リモートサーバーとローカルホストの間で通信を安全に中継する技術です。これにより、ローカルまたはリモートの特定のポートで動作するサービスにアクセスすることができます。
ローカルポートフォワーディング | ローカルマシンのポートを介して、リモートサーバー上の特定のポートにアクセスします。 | ファイアウォールで制限されているリモートサービスにアクセスする。 VPNを使わずにリモートリソースを利用する。 |
リモートポートフォワーディング | リモートサーバー側のポートを介して、ローカルマシンまたは別のリモートホスト上の特定のポートにアクセスします。 | ローカルホスト上で動作するサービスを、リモートサーバーからアクセス可能にする。 ローカル開発環境をリモートユーザーに提供する。 |
ダイナミックポートフォワーディング | プロキシサーバーとしてSSHを使用し、複数の宛先にトンネルを作成します。これはSOCKSプロキシとして機能します。 | セキュアなブラウジング(例えば、特定の国で制限されているサイトにアクセス)。 リモートネットワークの複数サービスを一度に利用。 |
はじめに
社内ネットワークや学校などの環境では、インターネットアクセスの管理・セキュリティ強化のためにプロキシサーバーが導入されていることがよくあります。ときにはこのプロキシサーバーのフィルタリング機能が原因で、特定のWebサイトやサービスにアクセスできなくなることも。
プロキシサーバー(proxy server)とは、ユーザーのリクエスト(通信)を“代理”として受け取り、インターネット上の目的のサーバーにアクセスする役割を担うサーバーのことです。
一般的に、以下の目的で利用されます。
キャッシュ機能: よくアクセスするサイトのデータをプロキシ側で一時保存し、ネットワーク帯域を節約する。
セキュリティ・フィルタリング: 社内ルールや企業ポリシーに反するサイトや有害サイトへのアクセスを制限する。
ログ管理: どの端末がどのサイトにアクセスしているかを記録・監視する。
プロキシサーバーにはURLやキーワードをもとにアクセスを許可・ブロックする機能が搭載されている場合があります。たとえば「SNSサイト禁止」「特定のカテゴリのサイトは禁止」のように設定されている場合、該当のサイトにアクセスしようとすると、ブラウザ上にブロック画面が表示されたり“接続エラー”になったりすることがあります。
プロキシサーバーが原因で特定のサイトにアクセスできないとき、以下のような症状が現れることが多いです。
接続タイムアウト: ブラウザ上でエラーが表示され、ページがまったく表示されない。
ブロック画面が表示される: 「このサイトはアクセス禁止です」などの専用ページが表示される。
部分的なリソースの読み込み失敗: サイトは見られるが、画像や特定のスクリプトだけが読み込めない。
こういった場合、プロキシサーバーの管理画面を確認できれば、どのURLがブロックされたのか一目でわかることがあります。しかし、組織の方針などで管理画面にアクセスできないケースも少なくありません。
ここでは、プロキシサーバーの管理画面に頼らずに、Chromeの開発者ツール(検証ツール)などを使ってフィルタリングの原因を調べる方法を紹介します。
他のブラウザ(Firefox、Edgeなど)でも同様の手順で確認できますが、ここではChromeを例とします。
Chromeを開き、アクセスしたいサイトに移動。
キーボードのF12キー、または右上のメニューから「その他のツール > デベロッパーツール」を選択。
開発者ツールを開いたら、上部のタブからNetworkを選択する。
すでにページが読み込まれている場合は、いったんリロード(もしくは「Disable cache」にチェックを入れてリロード)する。
すると、該当サイトの読み込みリクエストの一覧が表示される。
ここで注目すべきは、
ステータスコード: 200(OK)、404(Not Found)、403(Forbidden)、407(Proxy Authentication Required)など
URL: ブロックの原因となっているかもしれないドメインやファイルパス
もしフィルタリングが作動している場合、特定のリソースだけ403や404、あるいは proxy error が返っているといったことが確認できるでしょう。
Networkタブにエラーがない場合でも、Consoleタブにエラーメッセージが残っていることがあります。
ブロックされたリソースのURL
「Failed to load resource: the server responded with a status of 403」などの文言
これらのエラー文言から、フィルタリングによるエラーかどうか判断材料になります。
NetworkタブやConsoleタブで見つけたエラーURLに対して、
ブラウザのアドレスバーに直接貼り付けてアクセス
「このURL自体がブロックされているのか」を確認
これにより、主サイト自体は表示できても、広告や解析用のドメインがブロックされてページが中途半端に崩れているというケースも特定できるはずです。
もし開発者ツールだけでは原因が分かりづらい場合、curlやpingなどのコマンドラインツールを使っても検証ができます。
ターミナルやコマンドプロンプトで下記のように入力します:
curl -I http://アクセスしたいサイトのURL
ヘッダー情報(ステータスコードなど)が返ってきますが、もしプロキシが介入している場合は「プロキシのエラーメッセージ」「Access Denied」などが返ってくることがあります。
ping アクセスしたいサイトのドメイン
pingはICMPプロトコルを使っているため、Webアクセスのフィルタリングと挙動が異なることもありますが、サーバー側に全く応答がないのか、そもそもDNS解決できないのか、といった点を切り分けるヒントになります。
adobe ホワイトリスト プロキシサーバー 拡張機能 参照URL一覧
Webページが読み込む全てのリソース(画像、スクリプト、スタイルシート、API呼び出しなど)のURLを取得
page-resources-monitor/
├── manifest.json # 拡張機能の設定ファイル
├── background.js # バックグラウンドでリクエストを監視
├── popup.html # ポップアップのUI
└── popup.js # ポップアップの動作制御
manifest.json
{
"manifest_version": 3,
"name": "Page Resources Monitor",
"version": "1.0",
"description": "ページが読み込むすべてのリソースのURLを表示します",
"permissions": [
"activeTab",
"declarativeNetRequest",
"scripting",
"webRequest",
"webNavigation"
],
"host_permissions": [
"*://*/*",
"https://*/*"
],
"action": {
"default_popup": "popup.html"
},
"background": {
"service_worker": "background.js",
"type": "module"
}
}
background.js
let requests = new Map();
function addRequest(tabId, requestData) {
if (!requests.has(tabId)) {
requests.set(tabId, []);
}
requests.get(tabId).push(requestData);
}
chrome.webRequest.onBeforeRequest.addListener(
function(details) {
console.log('Captured request:', details);
if (details.tabId !== -1) {
const requestData = {
url: details.url,
type: details.type,
method: details.method || 'GET',
timestamp: details.timeStamp,
initiator: details.initiator || '',
frameId: details.frameId || 0,
parentFrameId: details.parentFrameId || 0,
documentUrl: details.documentUrl || ''
};
addRequest(details.tabId, requestData);
}
},
{
urls: ["<all_urls>"],
types: [
"main_frame",
"sub_frame",
"stylesheet",
"script",
"image",
"font",
"object",
"xmlhttprequest",
"ping",
"csp_report",
"media",
"websocket",
"other"
]
}
);
// タブが切り替わった時のリクエスト情報保持
chrome.tabs.onActivated.addListener(function(activeInfo) {
if (!requests.has(activeInfo.tabId)) {
requests.set(activeInfo.tabId, []);
}
});
// メッセージリスナー
chrome.runtime.onMessage.addListener(
function(request, sender, sendResponse) {
if (request.action === "getRequests") {
chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
if (tabs && tabs[0]) {
const currentTabId = tabs[0].id;
const tabRequests = requests.get(currentTabId) || [];
sendResponse({requests: tabRequests});
} else {
sendResponse({requests: []});
}
});
return true;
}
}
);
popup.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<style>
body {
width: 800px;
padding: 10px;
}
.resource-list {
max-height: 600px;
overflow-y: auto;
}
.resource-item {
margin: 5px 0;
padding: 5px;
border-bottom: 1px solid #eee;
}
.type-filter {
margin: 10px 0;
}
.url {
word-break: break-all;
}
.type {
color: #666;
font-size: 0.9em;
}
button {
margin: 5px;
padding: 8px 16px;
background-color: #4caf50;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
button:hover {
background-color: #45a049;
}
#exportCSV {
background-color: #2196f3;
}
#exportCSV:hover {
background-color: #1976d2;
}
#exportJSON {
background-color: #ff9800;
}
#exportJSON:hover {
background-color: #f57c00;
}
.stats {
margin: 10px 0;
padding: 10px;
background: #f5f5f5;
}
</style>
</head>
<body>
<h3>ページリソース一覧</h3>
<div class="type-filter">
<label>リソースタイプでフィルター:</label>
<select id="typeFilter">
<option value="all">すべて</option>
<option value="script">スクリプト</option>
<option value="stylesheet">スタイルシート</option>
<option value="image">画像</option>
<option value="font">フォント</option>
<option value="xmlhttprequest">API/XHR</option>
<option value="websocket">WebSocket</option>
<option value="other">その他</option>
</select>
</div>
<div class="stats" id="stats"></div>
<button id="copyButton">URLリストをコピー</button>
<button id="exportCSV">CSVでエクスポート</button>
<button id="exportJSON">JSONでエクスポート</button>
<div id="resourceList" class="resource-list"></div>
<script src="popup.js"></script>
</body>
</html>
popup.js
document.addEventListener('DOMContentLoaded', function() {
const resourceList = document.getElementById('resourceList');
const typeFilter = document.getElementById('typeFilter');
const stats = document.getElementById('stats');
const copyButton = document.getElementById('copyButton');
let allRequests = [];
function updateList(requests, filterType = 'all') {
resourceList.innerHTML = '';
if (!Array.isArray(requests)) {
console.error('requests is not an array:', requests);
requests = [];
}
const filteredRequests = filterType === 'all'
? requests
: requests.filter(r => r.type === filterType);
filteredRequests.forEach(request => {
const div = document.createElement('div');
div.className = 'resource-item';
div.innerHTML = `
<div class="url">${request.url}</div>
<div class="type">タイプ: ${request.type} (${request.method || 'GET'})</div>
<div class="initiator">発信元: ${request.initiator || '不明'}</div>
`;
resourceList.appendChild(div);
});
const typeCount = requests.reduce((acc, r) => {
acc[r.type] = (acc[r.type] || 0) + 1;
return acc;
}, {});
stats.innerHTML = `
<strong>統計情報:</strong><br>
総リクエスト数: ${requests.length}<br>
タイプ別: ${Object.entries(typeCount)
.map(([type, count]) => `${type}: ${count}`)
.join(', ')}
`;
}
function refreshData() {
chrome.runtime.sendMessage({action: "getRequests"}, function(response) {
console.log('Received response:', response);
if (response && response.requests) {
allRequests = response.requests;
updateList(allRequests, typeFilter.value);
}
});
}
refreshData();
setInterval(refreshData, 3000);
typeFilter.addEventListener('change', function() {
updateList(allRequests, this.value);
});
copyButton.addEventListener('click', function() {
const urls = allRequests.map(r => r.url).join('\n');
navigator.clipboard.writeText(urls).then(function() {
copyButton.textContent = 'コピーしました!';
setTimeout(function() {
copyButton.textContent = 'URLリストをコピー';
}, 2000);
});
});
});
chrome://extensions/
を開きますWebRequest APIは、Chrome拡張機能がブラウザのネットワークリクエストを監視・分析できるAPIです。
取得できる情報:
WordPressのオリジナルテーマを外注して納品してもらった場合でも、ちょっとしたテキスト修正やデザイン調整など軽微な変更は必要になることがあります。
本記事では、管理画面での一般的な修正方法と、万が一に備えてのバックアップ方法についてまとめました。
Contents
WordPress管理画面の「外観 > テーマファイルエディター」から直接テーマファイルを編集することも可能です。
ただし、誤って重要なコードを消してしまうリスクや、運用中サイトが即時に壊れてしまう可能性があるため、以下の点に注意してください。
外注テーマがアップデートで修正される可能性がある場合、子テーマを作成してカスタマイズを行うのがおすすめです。
子テーマを利用すると、親テーマ(オリジナルテーマ)のアップデートがあっても、子テーマ部分の修正は上書きされずに済むため、作業の手戻りを減らせます。
子テーマ作成の流れ(簡易版):
wp-content/themes
配下に、子テーマ用フォルダを新規作成 (例: originaltheme-child
)style.css
と functions.php
を設置し、style.css
の先頭に以下を追記functions.php
で親テーマのスタイルを読み込む記述を行う/*
Theme Name: Original Theme Child
Template: originaltheme
*/
WordPressサイトでは、万が一に備えて定期的なバックアップが重要です。以下は代表的な方法になります。
プラグインを利用することで、自動バックアップや復元がGUI操作で簡単に行えるようになります。
代表的なプラグインと機能の比較は下記の通りです。
プラグイン名 | 無料/有料 | 特徴 | 復元方法 |
---|---|---|---|
UpdraftPlus | 無料 / 有料 | 手動&自動バックアップ対応、クラウド連携可 | ダッシュボードから可 |
BackWPup | 無料 / 有料 | 定期実行スケジュール設定が豊富 | 手動インポートが必要 |
All-in-One WP Migration | 無料 / 有料 | サイト移行もできるバックアップ機能 | プラグインから一括復元 |
サーバーコントロールパネルやFTPソフトを使って手動でファイルとデータベースをダウンロードする方法です。
wp-content
など含む).sql
ファイルをローカルにダウンロードテーマファイルやプラグインコードの変更が多い場合には、Gitなどを使ってバージョン管理しておくと便利です。
前提:オリジナルテーマはGithubで管理
本番:さくらレンタルサーバー
WordPress開発環境をDockerで構築
WordPressデータベース同期方法の比較
【プラグイン】All-in-One WP Migration | ・サイズ制限があり(無料版)、小規模サイト向け ・ユーザーデータも含めて移行される |
phpMyAdmin | ・MySQLをブラウザ上で管理するためのツール |
WP CLI | ・CLI(コマンドラインインターフェース) |
エクスポートとはデータベースの中身を「SQLファイル」として保存することです。
本番のさくらインターネットのレンタルサーバーにssh接続します
wp-cliを使うには、WordPressのルートディレクトリで実行する必要があります。
$ wp db size
$ wp db tables
wp_hello_actionscheduler_actions
wp_hello_actionscheduler_claims
wp_hello_actionscheduler_groups
wp_hello_actionscheduler_logs
wp_hello_commentmeta
wp_hello_comments
wp_hello_links
wp_hello_options
wp_hello_postmeta
wp_hello_posts
wp_hello_term_relationships
wp_hello_term_taxonomy
wp_hello_termmeta
wp_hello_terms
wp_hello_usermeta
wp_hello_users
ホームディレクトリ(公開ディレクトリでない)場所にエクスポート
# ホームディレクトリに移動
cd ~
# バックアップ用ディレクトリ作成
mkdir db_backup
[xx@wwwxx ~/www/wp]$ wp db export --tables=wp_hello_posts,wp_hello_postmeta,wp_hello_terms,wp_hello_term_taxonomy,wp_hello_term_relationships,wp_hello_options ~/db_backup/content_backup.sql
WordPressのデータベースを簡単に検索・置換するためのPHPスクリプトです。WordPressサイトを移行する際や、URLやパスを変更する必要がある場合に非常に便利です。
レンタルサーバの管理画面からphpMyAdminを開きます
(ログイン情報wp-config.php
に記載されています)
使用しているDBを選択します
wp~で始まるテーブル名の一覧が表示されている状態を確認(テーブル名がwp4b164fで始まっているのは、WordPressインストール時に設定されたテーブル接頭辞($table_prefix)が適用されてるためです)
phpMyAdminの画面上部メニューにある「エクスポート」を選択します
utf8mb4
やutf8
エンコーディングを使用しており、エンコーディングを変更すると文字化けの原因になる可能性があります。(注意)db接続情報まで書き換えてしまうと接続エラーになります
https://interconnectit.com/search-and-replace-for-wordpress-databases
上記サイトよりフォームを送信すると、しばらくするとメールが送られてきます。
メールにZipファイルのダウンロードURLが記載されてます
Githubからダウンロードも可能
https://github.com/interconnectit/Search-Replace-DB
Search Replace DB をWordPress移行先に配置
WordPressをインストールしているディレクトリに(wp-config.phpと同じ階層に配置)
ブラウザでアクセスすると下記が表示されます(ディレクトリ名が長いので変更したほうが便利)
DB接続情報を「wp-config.php」より入力
※DB_HOST
の値にポート番号が含まれていないため、デフォルトのMySQLポート番号(3306
)が使用されます。
– phpMyAdminのDBインポートは圧縮しないとエラーがでる
実行前に「Dry Run」モードで変更内容を確認します。
問題がなければ検索・置換を実行します。
– DB置換後、移動元のURLに遷移する場合はキャッシュが原因の場合があります、シークレットモードで確認
– search db repはDockerfileでphpのバージョンを7.4に変更する必要がある?
FROM wordpress:php7.4-apache
– db情報はdocker-compose.ymlの内容を使用※wp-config.phpの内容は使用しない
WORDPRESS_DB_HOST: db
WORDPRESS_DB_USER: wordpress
WORDPRESS_DB_PASSWORD: wordpress
WORDPRESS_DB_NAME: wordpress
WordPress Search Replace DB の使い方
https://www.webdesignleaves.com/pr/wp/wp_search_replace_db.html
WordPressでInstagramの投稿を簡単に表示できるプラグインといえば「Smash Balloon Instagram Feed(旧称:Instagram Feed)」が有名ですよね。しかし、アップデートやインスタグラムのAPI仕様変更などが原因で、たまに次のようなエラーに遭遇することがあります。
“instagram フィードで重大な問題”
もしくは
“Instagram Feed: There is a critical error on this site.”
このようなエラーが発生すると、Instagramの投稿がサイトに表示されなくなってしまうため、見栄えや運用上の問題も大きく、すぐに対処が必要となります。本記事では、このエラーの原因や具体的な対処方法、注意点をご紹介します。
「Smash Balloon Instagram Feed」は、Instagram APIを通じて投稿を取得し、WordPressサイトに埋め込むプラグインです。エラー文として「instagram フィードで重大な問題」「Instagram Feed error」などが表示される場合、以下のような原因が考えられます。
ワードプレスプラグイン『Smash Balloon Instagram Feed』でエラーが出る…!エラーを消してインスタグラムをちゃんと表示させたい。
https://granvi.jp/marketing/smash-balloon-instagram-feed-error
2024年12月4日以降にInstagramフィードが表示できなくなったWordPressサイトの対応
https://lofir.net/instagram-feed-2024-12/
Instagramの投稿をホームページに埋め込む方法を分かりやすく解説
https://www.xserver.ne.jp/bizhp/insert-instagram/?gad_source=1&gclid=Cj0KCQiAvbm7BhC5ARIsAFjwNHs2b9fn9-6XWK4I16jy3SyiT_bZ2XrgxDj6bbsPdFY9sTeHOELCxNAaAvxEEALw_wcB
ローカル環境に Node.js をインストールせず、Docker コンテナ内で npm init や npm install を行うやり方です。
(最初にコンテナを作って、その中でプロジェクトを初期化し、あとからローカルにファイルを持ってくるイメージです。)
mkdir node-docker-init
cd node-docker-init
cat << 'EOF' > Dockerfile
FROM node:18-alpine
# 作業ディレクトリ設定(まだソースはコピーしない)
WORKDIR /usr/src/app
# とりあえずシェルでコンテナ起動できるようにする
CMD ["sh"]
EOF
<!-- ファイル送信用のフォーム -->
<form method="POST" enctype="multipart/form-data">
<input type="file" name="uploadFile">
<button>送信</button>
</form>
enctype="multipart/form-data"
という部分がないとファイルを正しく送信できません。
なぜなら:
▼プレビュー
ユーザーがこのフォームで「ファイルを選択」して「アップロード」ボタンを押すと、選択したファイルがサーバーに送られます。
<ファイルアップロードのフロー>
<POSTリクエストの違い>
<サーバーサイドのフロー>
Gitを使用したチーム開発において、feature branchとmain branchの統合は重要なポイントとなります。この記事では、安全かつ効率的なブランチ統合の戦略について解説します。
Contents
Feature branchで開発を進める中で、main branchの変更を早めに取り込むことには、いくつかの重要な利点があります:
まず、feature branchで作業中に、main branchの変更を取り込みます:
# 現在のブランチを確認
git branch
# mainの変更を取り込む
git merge main
この時点でコンフリクトが発生した場合の対処:
git add <コンフリクトを解決したファイル>
git commit -m "Resolve conflicts with main"
コンフリクト解決後の重要なステップ:
すべての確認が完了したら、main branchへの統合を行います:
# mainブランチに切り替え
git checkout main
# featureの変更を取り込む
git merge feature
この方法は、特に以下のような状況で効果を発揮します:
早めにmainの変更を取り込んで確認することで、最終的な統合をより安全に行うことができます。これは、モダンなGit開発フローにおける重要なプラクティスの一つと言えるでしょう。
Docker環境があれば、Python実行環境を新たにインストールする必要はありません
確認すべき重要な点:
// 例:こういった形でAPIの型定義を作成すると良い
interface ApiResponse {
status: string;
data: {
id: number;
name: string;
// その他の項目
}[];
}
.gitignore
ファイルの更新:Copynode_modules
.env
.env.local
.DS_Store
package.json
のscripts
セクションを確認/追加:jsonCopy{
"scripts": {
"start": "node src/index.js",
"build": "npm install",
"vercel-build": "npm run build"
}
}
デプロイ手順:
bashCopygit init
git add .
git commit -m "Initial commit"
git remote add origin <your-github-repo-url>
git push -u origin main
AWS_ACCESS_KEY_ID=xxxxx AWS_SECRET_ACCESS_KEY=xxxxx AWS_REGION=ap-northeast-1
これで自動的にデプロイが開始され、完了すると公開URLが提供されます。その後はGitHubにプッシュするたびに自動デプロイが実行されます。
Contents
「Atua」というテーマは比較的新しく、インストール数も多くなくあまりメジャーではないといえます。
そのため情報が少なく下記の手段で調べる必要がありそうです
参考に「Lightning」と比較し見ると下記です
Atua | Lightning | |
---|---|---|
バージョン | 1.0.98 | 15.29.0 |
Active installations | 600+ | 100,000+ |
PHP version | 5.6 | 7.4 |
WordPress version | 4.7 | 6.4 |
Atuaを有効化するとDesert Companion プラグインのインストール、有効化するようポップアップがでます
有効化するとフロントページにコンテンツが表示されます
WordPressのテンプレート階層通りfront-page.phpやhome.phpが使用されてはいません。
Desert Companion プラグインでテンプレートファイルが指定されています(desert-companion\inc\desert-companion-activator.php)
atua/page-templates/frontpage.phpがフロントページでテンプレートファイルとして使用されます
下記ページでテーマファイルはダウンロード可能です
子テーマをインストールするだけで大分デザインが変更されます
wp-content/themes/atua-child/を作成
ディレクトリ構成
atua-child
┗┳ style.css
┣ functions.php
┣
style.css
/*
Theme Name: atua Child
Theme URI:
Description: Child theme for atua theme
Author:
Author URI:
Template: atua
Version: 1.0.0
License: GNU General Public License v2 or later
License URI: http://www.gnu.org/licenses/gpl-2.0.html
Text Domain: atua-child
*/
WordPress管理画面でテーマ追加画面で子テーマとして認識されます
(注意)テーマを有効化するとフロントページのコンテンツが表示されなくなってしまいます。
もともと親テーマの「Atua」プラグインの「Desert Companion」でトップページのコンテンツを表示していました
<調査>
デバッグログでDesert_Companion_Atua_frontpage
アクションにコールバックが登録されていないことを確認
frontpage.php
<?php
/**
* Template Name: Frontpage
*/
// ファイルが実行されていることを確認
error_log('Frontpage template is being executed at: ' . date('Y-m-d H:i:s'));
echo '<!-- Child Theme Frontpage Template is loaded -->';
get_header();
?>
<div id="content" class="content-area">
<main id="main" class="site-main">
<?php
// 現在のテーマ情報をログに出力
$current_theme = wp_get_theme();
error_log('Current theme: ' . $current_theme->get('Name'));
error_log('Parent theme: ' . $current_theme->parent()->get('Name'));
// プラグインの初期化状態を確認
error_log('Checking plugin initialization:');
error_log('Template Directory: ' . get_template_directory());
error_log('Stylesheet Directory: ' . get_stylesheet_directory());
error_log('Theme Root: ' . get_theme_root());
// デバッグ情報を追加
global $wp_filter;
if (isset($wp_filter['Desert_Companion_Atua_frontpage'])) {
error_log('Registered callbacks for Desert_Companion_Atua_frontpage:');
error_log(print_r($wp_filter['Desert_Companion_Atua_frontpage'], true));
} else {
error_log('No callbacks registered for Desert_Companion_Atua_frontpage');
}
// プラグインの読み込み状態を確認
error_log('Desert Companion Plugin Path: ' . (defined('desert_companion_plugin_dir') ? desert_companion_plugin_dir : 'Not defined'));
// アクションの実行
do_action('Desert_Companion_Atua_frontpage');
?>
</main>
</div>
<?php
get_footer();
<原因>
ログの結果frontpage.phpの下記コードは「Desert_Companion_Atua_frontpage」というタイミングで何かを実行しようとしていましたが、実行すべき関数(コールバック)が登録されていない状態でした。
do_action('Desert_Companion_Atua_frontpage');
なぜかというと
atua-child
になるため条件を満たさず設定ファイルが読み込まれないDesert_Companion_Atua_frontpage
にコールバックが登録されない// desert-companion/desert-companion.phpの中で
if( 'Atua' == $desert_activated_theme->name){
require desert_companion_plugin_dir . 'inc/themes/atua/atua.php';
}
子テーマのfunctions.php
<?php
/**
* Atua Child Theme functions and definitions
*/
/**
* プラグインの初期化を早い段階で強制的に実行
*/
function force_parent_theme_companion() {
// プラグインのパスを直接指定して読み込み
if (defined('desert_companion_plugin_dir')) {
require_once desert_companion_plugin_dir . 'inc/themes/atua/atua.php';
// 初期化関数が存在する場合は実行
if (function_exists('desert_companion_init')) {
desert_companion_init();
}
}
}
// できるだけ早い段階で実行
add_action('init', 'force_parent_theme_companion', 0)
この関数が効果を発揮した理由:
Contents
「Maintenance」プラグイン
.htaccess
ファイルで全体にアクセス制限をかける.htaccess
ファイルを編集FTPソフトを使ってWordPressのルートディレクトリ(wp-config.php
がある場所)にある .htaccess
ファイルを編集します。
# メンテナンスモード設定開始
RewriteEngine On
# メンテナンスページ (maintenance.html) へのアクセスは除外
RewriteCond %{REQUEST_URI} !/maintenance.html$
# 全てのアクセスを maintenance.html にリダイレクト
RewriteRule ^(.*)$ /maintenance.html [R=302,L]
# メンテナンスモード設定終了
# WordPressが /wordpress/ にインストールされている場合
RewriteEngine On
# maintenance.html へのアクセスは除外
RewriteCond %{REQUEST_URI} !^/wordpress/maintenance.html$
# すべてのアクセスを maintenance.html にリダイレクト
RewriteRule ^(.*)$ /wordpress/maintenance.html [R=302,L]
WordPressのルートディレクトリに maintenance.html
ファイルを作成し、メンテナンス案内ページとしてアップロードします。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>メンテナンス中</title>
<!-- CSSスタイルで簡単なデザイン -->
<style>
body {
text-align: center;
font-family: Arial, sans-serif;
background-color: #f4f4f4;
margin: 0;
padding: 0;
}
.container {
margin-top: 20%;
}
h1 {
color: #333;
font-size: 2em;
}
p {
color: #666;
font-size: 1.2em;
}
</style>
</head>
<body>
<div class="container">
<h1>メンテナンス中</h1>
<p>現在、サイトはメンテナンス中です。しばらくお待ちください。</p>
</div>
</body>
</html>
注意: この方法は「検索エンジン」には非表示にできますが、直接URLにアクセスすれば閲覧可能です。
リモートデスクトップ先のChromeでマウスカーソルが消える
https://blog.litus.co.jp/2019/07/chrome.html
Contents
「マクロ」は2つの意味があります
「マクロ」はExcelを自動処理してくれるもExcelの機能です。
そして「VBA(Visual Basic for Applications)」という言語で命令書を作成することを「マクロを組む」といいます。
再度押すと記録再開されます。
記録したマクロを選択し、「実行」すると記録した処理が実行されます
「編集」でVBEを起動して編集できます
「開発」の項目はExcelインストールしたばかりの状態だとだとはチェックが外れいるので有効化しておきます。
表示された「開発」タブから「Visual Basic Editor」をクリック
[Alt] + [F11]でも開けます
Excelのセルやワークシートを操作できるだけでなく、悪意のある者が作成すればパソコンに障害を引き起こすような動作もプログラミングすることができます。
そうした悪意のあるマクロが勝手に実行されないように、セキュリティレベルを設定する機能があります。
[ファイル] → [オプション] → [トラスト センター]
既定では1度目のみ警告が表示される
VBEでプロシージャを実行すると警告が出る
マクロ動作の基本単位「プロシージャ」には「Sub」と「Function」があります。
**サブプロシージャ(Sub)**は、「処理手順のまとまり」を表すひとまとまりのコードブロックです。「この手順を実行してね」と指示できる単位と考えると分かりやすいです。Subは処理を実行するための命令文のかたまりであり、戻り値を返しません。
Sub サブ名()
' ここに実行したい処理を書きます
End Sub
処理内容はインデント(字下げ)するのがお作法です
Function
で始まりEnd Function
で終わる関数名 = 値
)戻り値 = 関数名(引数)
」という形で呼び出して、計算結果などを受け取ることができますContents
Amazon Bedrockの生成AIを活用した、VBAマクロコードを自動的にフォーマットするWebアプリケーション。インデント、変数名、コメントなどを統一的なルールで整形します。
npm install
npm run dev
http://localhost:3000
vba-formatter/
├── src/
│ ├── index.js # メインサーバーファイル
│ ├── formatter.js # 基本的なフォーマット処理
│ ├── bedrock/ # Bedrock関連の新規ディレクトリ
│ │ ├── client.js # Bedrock クライアント設定
│ │ ├── prompts.js # プロンプトテンプレート
│ │ └── analyzer.js # コード分析ロジック
│ └── public/ # フロントエンド
.env
AWS_ACCESS_KEY_ID=あなたのアクセスキー
AWS_SECRET_ACCESS_KEY=あなたのシークレットキー
AWS_REGION=us-east-1
dotenv
は、.env
ファイルを読み込んで、その内容を環境変数としてprocess.env
に設定するためのパッケージです。
npm install dotenv
@aws-sdk/client-bedrock-runtime
: Amazon Bedrockと通信するため下記インストール
npm install dotenv @aws-sdk/client-bedrock-runtime
client.js
:Amazon Bedrockへの接続設定
require('dotenv').config();
const { BedrockRuntimeClient } = require("@aws-sdk/client-bedrock-runtime");
// Bedrockクライアントの設定
const client = new BedrockRuntimeClient({
region: process.env.AWS_REGION || "us-east-1",
credentials: {
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY
}
});
module.exports = client;
prompts.js
:AIへの指示(プロンプト)テンプレート
/**
* prompts.js
* AIへの指示内容を定義するファイル
* VBAコードの分析と改善の方法をAIに指示します
*/
// AIへの指示内容を定義(バッククォートで囲むことで複数行の文字列を作成)
const ANALYSIS_PROMPT = `
# ここからがAIへの指示内容です
=============================================
# AIの役割定義
あなたはVBAコードの専門家として、以下のコードを分析し、優先順位に従って改善してください。
# 入力コード部分
\`\`\`vba
{code}
\`\`\`
# 改善要件(優先順位:高 - 第一弾)
=============================================
1. インデント処理
- SUBからENDSUBまでTABひとつ分のインデント
- IF文、With文、For文等の制御構造にもインデント
- ネストレベルに応じて適切なインデント
2. ヘッダー・フッター追加
- Sub開始時のヘッダー:
Option Explicit
'***************************************************
' [プロシージャ名]
'***************************************************
'【機 能】[機能の説明]
'【引 数】[引数の説明]
'【戻 り 値】[戻り値の説明]
'【機能説明】[詳細な説明]
'【備 考】[その他特記事項]
' Copyright(c) 2024 Your Company All Rights Reserved.
'***************************************************
- 処理の区切り:
'***************************************************
' 変数宣言
'***************************************************
[変数宣言部分]
- Sub終了時のフッター:
'***************************************************
'---------------------------------------------------
' Version
'---------------------------------------------------
' 1.00 | 2024.01.01 | ********* | *********
'***************************************************
# 改善要件(優先順位:中 - 第二弾)
=============================================
3. 変数名の改善
- 一文字の変数を禁止
- セル参照の変数は意味のある名前に変更
(例: Last_Row, Last_Column)
- 配列はArrayを付ける
- その他の変数はDataを付ける
4. Public変数・Call文の処理
- Public変数にはPublic関数であることをコメントで記載
- Call文にはモジュール名を追加
(例: Call Module1.印刷)
5. 変数宣言のコメント
- 宣言した変数の横にコメントを追加
- 使用目的や内容を簡潔に説明
# 改善要件(優先順位:中 - 第三弾)
=============================================
6. 変数・シート名の処理
- 日本語変数名を英語に変更
- 未宣言変数を「変数宣言」セクションに追加
- 型判定できない変数はVariantに
- シート名は定数(Const)で定義
- シート追加時の名前はVariantで処理
7. コメント追加
- IF文、With文などの制御構造にコメント
- 配列の内容説明
- SET文の説明
- Offsetのコメント必須
(参照セルと目的を明記)
8. コードの最適化
- 類似コードが3回以上続く場合はループ化
- パスの直書きは避け、pathに置換
(元のパスはコメントとして保持)
# 出力形式
=============================================
優先順位の高い要件(第一弾)から順に適用し、
改善したVBAコードのみを出力してください。
`;
module.exports = {
ANALYSIS_PROMPT
};
analyzer.js
:Bedrockとの通信処理
// AWS SDKからBedrockのクライアントとコマンドをインポート
const { InvokeModelCommand } = require("@aws-sdk/client-bedrock-runtime");
const client = require('./client');
const { ANALYSIS_PROMPT } = require('./prompts');
/**
* VBAコードをClaudeモデルで分析する関数
* @param {string} vbaCode - 分析対象のVBAコード
* @returns {Promise<string>} - 改善されたVBAコード
*/
async function analyzeCode(vbaCode) {
try {
// プロンプトテンプレートにVBAコードを挿入
const prompt = ANALYSIS_PROMPT.replace('{code}', vbaCode);
// Claude用のリクエストコマンドを作成
const command = new InvokeModelCommand({
// Claude 3 Sonnetモデルを指定
modelId: "anthropic.claude-3-sonnet-20240229-v1:0",
contentType: "application/json",
accept: "application/json",
// Claude用のリクエスト形式
body: JSON.stringify({
anthropic_version: "bedrock-2023-05-31",
max_tokens: 2048,
messages: [
{
role: "user",
content: prompt
}
]
})
});
// BedrockAPIを呼び出し
const response = await client.send(command);
// レスポンスの処理(Claude形式)
const responseBody = Buffer.from(response.body).toString('utf8');
const jsonResponse = JSON.parse(responseBody);
// Claudeのレスポンス形式から結果を抽出
if (jsonResponse.content && jsonResponse.content[0]) {
return jsonResponse.content[0].text;
}
throw new Error('AIモデルからの応答が不正な形式です');
} catch (error) {
console.error("コード分析エラー:", error);
throw error;
}
}
module.exports = {
analyzeCode
};
src/index.js
:Webサーバーのメイン処理
// ========================================
// サーバーのメインファイル (index.js)
// VBAフォーマッターのバックエンド処理を担当
// ========================================
// 必要なモジュールのインポート
const express = require('express');
const { analyzeCode } = require('./bedrock/analyzer'); // Bedrock AIの分析機能
const { formatVBA } = require('./formatter'); // 基本的なフォーマット機能
const { ANALYSIS_PROMPT } = require('./bedrock/prompts'); // AIプロンプトの取得
// Expressアプリケーションの初期化
const app = express();
// ミドルウェアの設定
// 静的ファイル(HTML/CSS/JS)の提供設定
app.use(express.static('src/public'));
// JSONリクエストの解析を有効化
app.use(express.json());
/**
* AIへのプロンプト内容を提供するエンドポイント
* フロントエンドでAIへの指示内容を表示するために使用
*/
app.get('/prompts', (req, res) => {
res.json({
success: true,
prompt: ANALYSIS_PROMPT
});
});
/**
* VBAコード分析API
* フロントエンドから送信されたコードを分析・改善
*/
app.post('/format', async (req, res) => {
try {
// リクエストからコードを取得
const { code } = req.body;
// 入力チェック
if (!code) {
return res.status(400).json({
success: false,
error: 'コードが提供されていません'
});
}
// 1. 基本的なフォーマットを適用
const basicFormatted = formatVBA(code);
// 2. AIによる高度な分析と改善
const aiAnalysis = await analyzeCode(basicFormatted);
// 成功時のレスポンス
res.json({
success: true,
formatted: aiAnalysis
});
} catch (error) {
// エラー発生時の処理
console.error('エラーが発生しました:', error);
res.status(500).json({
success: false,
error: error.message
});
}
});
// サーバーの起動設定
const PORT = process.env.PORT || 3000;
// 全てのIPアドレスからのアクセスを許可(社内ネットワーク用)
app.listen(PORT, '0.0.0.0', () => {
console.log('========================================');
console.log(`VBA Formatter サーバー起動`);
console.log(`http://localhost:${PORT}`);
console.log('========================================');
});
public/index.html
:ユーザーインターフェース
<!DOCTYPE html>
<html lang="ja">
<head>
<!-- 文字エンコーディングとビューポートの設定 -->
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>VBA Formatter</title>
<!-- reset.css ress -->
<link rel="stylesheet" href="https://unpkg.com/ress/dist/ress.min.css" />
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="style.css">
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+JP:wght@100..900&display=swap" rel="stylesheet">
<!-- スタイルシートの読み込み -->
<link rel="stylesheet" href="style.css">
</head>
<body>
<!-- メインコンテナ -->
<div class="container">
<h1>VBA Formatter</h1>
<!-- AIへの指示内容表示セクション(src/bedrock/prompts.jsの内容) -->
<div class="prompt-section">
<h3>AIへの指示内容</h3>
<!-- プロンプト内容を動的に表示する要素 -->
<div class="prompt-display-container">
<div class="user-icon"></div>
<div class="prompt-display-wrapper">
<pre class="prompt-display">ユーザーからの指示内容がここに表示されます</pre>
</div>
</div>
</div>
<!-- コードエディタ部分 -->
<div class="editors">
<!-- 入力用エディタ -->
<div class="editor">
<h3>分析するコード</h3>
<!-- 入力用テキストエリア:ユーザーがコードを入力または貼り付け可能 -->
<textarea id="input" placeholder="VBAコードを入力してください"></textarea>
<!-- ファイル選択ボタン:.basと.txtファイルのみ受付 -->
<input type="file" id="vbaFile" accept=".bas,.txt">
<!-- <span class="detail">※.basと.txtファイルのみ受付</span> -->
</div>
<!-- 出力用エディタ -->
<div class="editor">
<h3>改善後のコード</h3>
<!-- 出力用テキストエリア:読み取り専用で改善されたコードを表示 -->
<textarea id="output" readonly></textarea>
<!-- フォーマット実行ボタン -->
<button onclick="formatCode()">Format</button>
</div>
</div>
</div>
<!-- JavaScriptの処理 -->
<script>
// ページ読み込み時にプロンプト内容を取得
window.onload = async function() {
try {
const response = await fetch('/prompts');
const data = await response.json();
if (data.success) {
document.querySelector('.prompt-display').textContent = data.prompt;
}
} catch (error) {
console.error('プロンプト取得エラー:', error);
document.querySelector('.prompt-display').textContent = 'プロンプトの読み込みに失敗しました';
}
};
/**
* コードのフォーマットを実行する関数
* 1. 入力を取得
* 2. サーバーにリクエスト
* 3. 結果を表示
*/
async function formatCode() {
// テキストエリアから入力を取得
const input = document.getElementById('input').value;
// ステータス表示用の要素取得
const formatButton = document.querySelector('button');
// 入力値のバリデーション
if (!input) {
alert('VBAコードを入力してください');
return;
}
// 処理開始のステータス表示
formatButton.innerHTML = '<span class="processing"><span></span><span></span><span></span></span>';
try {
// サーバーへのリクエスト
const response = await fetch('/format', {
method: 'POST', // POSTメソッドを使用
headers: {
'Content-Type': 'application/json' // JSONデータとして送信
},
body: JSON.stringify({ code: input }) // 入力をJSONに変換
});
// レスポンスの処理
const data = await response.json();
// 成功時は結果を表示、失敗時はエラーメッセージを表示
if (data.success) {
document.getElementById('output').value = data.formatted;
formatButton.textContent = '分析完了!';
} else {
formatButton.textContent = 'エラー: ' + data.error;
}
} catch (error) {
// 通信エラーなどの例外処理
formatButton.textContent = 'エラー: ' + error.message;
}
formatButton.classList.remove('processing'); // アニメーションクラスを削除
}
/**
* ファイル選択時の処理
* 選択されたファイルの内容を入力エリアに表示
*/
document.getElementById('vbaFile').addEventListener('change', (e) => {
// 選択されたファイルを取得
const file = e.target.files[0];
if (file) {
// FileReaderを使用してファイルの内容を読み込み
const reader = new FileReader();
// ファイル読み込み完了時の処理
reader.onload = (e) => {
// 読み込んだ内容を入力エリアに設定
document.getElementById('input').value = e.target.result;
};
// ファイルをテキストとして読み込み開始
reader.readAsText(file);
}
});
</script>
</body>
</html>
public/style.css
:見た目の装飾
body {
font-family: "Noto Sans JP", serif;
background-color: #f1f2f3;
}
/* メインコンテナ */
.container {
max-width: 1200px;
margin: 0 auto;
padding: 0 2rem 2rem;
}
h1 {
background-color: #fff;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
text-align: center;
letter-spacing: 0.1em;
padding: 1rem 0;
margin: 0 calc(50% - 50vw);
width: 100vw;
margin-bottom: 2rem;
}
h3 {
margin-bottom: 1rem;
text-align: center;
letter-spacing: .1em;
}
/* AIへの指示内容表示部分 */
.prompt-section {
margin-bottom: 2rem;
}
.prompt-display-container {
display: flex;
align-items: end;
justify-content: center;
gap: 2rem;
margin: 0 auto;
}
.user-icon {
width: 40px;
height: 40px;
background-color: #f6ce55;
border-radius: 50%;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
margin-bottom: 3rem;
position: relative;
}
.user-icon::after {
content: "";
position: absolute;
top: 96%;
left: 50%;
transform: translateX(-50%);
width: 50px;
height: 40px;
background-color: #f6ce55;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
border-top-left-radius: 50%;
border-top-right-radius: 50%;
}
.prompt-display-wrapper {
max-width: 80%;
display: flex;
align-items: center;
justify-content: center;
background-color: #fff;
border-radius: 5rem 5rem 5rem 0;
box-shadow: 2px 2px 4px rgba(0, 0, 0, 0.1);
padding: 2rem;
}
.prompt-display {
font-family: monospace;
font-size: 0.825rem;
line-height: 1.5;
white-space: pre-wrap;
height: 300px;
overflow-y: scroll;
resize: vertical;
}
/* カスタムスクロールバーのスタイル */
.prompt-display::-webkit-scrollbar {
width: 0.5rem; /* スクロールバーの幅 */
}
.prompt-display::-webkit-scrollbar-thumb {
background: #888;
border-radius: 1rem;
}
/* エディタ部分 */
.editors {
display: flex;
gap: 2rem;
margin-bottom: 1rem;
}
.editor {
flex: 1;
display: flex;
flex-direction: column;
}
.editor > * {
margin-bottom: 1rem;
}
.editor > *:last-child {
margin-top: auto!important;
}
.editor input {
height: 3rem;
background-color: #ccc;
padding: 0.5rem 1rem;
border-radius: 4px;
width: 100%;
transition: all 0.3s ease;
}
.editor input:hover {
background-color: #bbb;
}
.editor .detail {
margin-top: 0.5rem;
color: #FF565D;
}
/* テキストエリア */
.editor textarea {
width: 100%;
height: 500px;
font-family: monospace;
padding: 10px;
font-size: 14px;
line-height: 1.5;
border: 2px solid #ccc;
background-color: #fff;
border-radius: 4px;
}
button {
height: 3rem;
padding: 0.5rem 1rem;
background-color: #e9be3b;
;
border-radius: 4px;
color: white;
letter-spacing: 0.1em;
border: none;
cursor: pointer;
}
button:hover {
background-color: #d3b458;
;
transition: all 0.3s ease;
}
/* アニメーションを追加するクラス */
.processing {
display: inline-flex;
gap: 0.5rem;
}
.processing span {
display: inline-block;
width: 0.75rem;
height: 0.75rem;
background-color: #fff;
border-radius: 50%;
animation: fadeDots 1.5s infinite ease-in-out;
}
/* それぞれのドットに異なるアニメーションの遅延を設定 */
.processing span:nth-child(1) {
animation-delay: 0s;
}
.processing span:nth-child(2) {
animation-delay: 0.2s;
}
.processing span:nth-child(3) {
animation-delay: 0.4s;
}
/* 薄さ(透明度)を変化させるアニメーション */
@keyframes fadeDots {
0%, 100% { opacity: 0.2; } /* 薄くなる */
50% { opacity: 1; } /* 濃くなる */
}
formatter.js
:VBAコードの基本的な整形処理
// VBAコードをフォーマットする関数
// 引数:code - フォーマットする元のVBAコードの文字列
// 戻り値:フォーマット済みのVBAコードの文字列
function formatVBA(code) {
// ステップ1: コードを行単位に分割
// splitは文字列を指定した区切り文字(ここでは改行)で配列に分割する
const lines = code.split('\n');
// ステップ2: インデントレベルを管理する変数を初期化
// インデントレベルは、コードのネストの深さを表す
// 例:Sub内なら1、If文の中なら2、というように増えていく
let indentLevel = 0;
// ステップ3: フォーマット済みの行を格納する配列を準備
const formattedLines = [];
// ステップ4: キーワードを定義
// これらの単語が行の先頭にあるかどうかでインデントを判断する
const startKeywords = ['sub', 'function', 'if', 'for', 'do'];
const endKeywords = ['end sub', 'end function', 'end if', 'next', 'loop'];
// ステップ5: 各行を処理
// forEachは配列の各要素に対して処理を行う
lines.forEach((line, lineNumber) => {
// 行の空白を削除して、小文字に変換(比較用)
const trimmedLine = line.trim();
const lowerLine = trimmedLine.toLowerCase();
// ステップ6: 空行の処理
if (trimmedLine === '') {
formattedLines.push('');
return; // 次の行の処理へ
}
// ステップ7: インデントレベルの調整
// 7.1: ブロックの終了を示すキーワードの前にインデントを減らす
if (endKeywords.some(keyword => lowerLine.startsWith(keyword))) {
indentLevel = Math.max(0, indentLevel - 1);
}
// ステップ8: 行のフォーマット
// 8.1: 現在のインデントレベルに応じてスペースを追加
// repeat()は文字列を指定回数繰り返す
const indent = ' '.repeat(indentLevel);
// 8.2: コメントの処理
// シングルクォートで始まる行はコメント
if (trimmedLine.startsWith("'")) {
formattedLines.push(indent + trimmedLine);
} else {
// 8.3: 通常のコードの処理
formattedLines.push(indent + trimmedLine);
}
// ステップ9: 次の行のためのインデントレベルの更新
// 9.1: ブロックの開始を示すキーワードの後でインデントを増やす
if (startKeywords.some(keyword => lowerLine.startsWith(keyword))) {
indentLevel++;
}
});
// ステップ10: フォーマット済みの行を改行文字で結合して1つの文字列に戻す
return formattedLines.join('\n');
}
// ステップ11: ヘッダーコメントを生成する関数
function createHeader() {
return [
"Option Explicit",
"'***************************************************",
"' VBAプログラム",
"'***************************************************",
"'【機 能】",
"'【返 り 値】なし",
"'【機能説明】",
"'【備 考】",
"' Copyright(c) " + new Date().getFullYear(),
"'***************************************************",
""
].join('\n');
}
// ステップ12: 変数をチェックして適切な型を提案する関数
function suggestVariableType(variableName) {
// 変数名から型を推測
if (variableName.toLowerCase().includes('count')) {
return 'Long';
} else if (variableName.toLowerCase().includes('date')) {
return 'Date';
} else if (variableName.toLowerCase().includes('flag')) {
return 'Boolean';
} else if (variableName.toLowerCase().includes('name')) {
return 'String';
} else {
return 'Variant';
}
}
// ステップ13: モジュールの外部公開
// このファイルの関数を他のファイルから使用できるようにする
module.exports = {
formatVBA,
createHeader,
suggestVariableType
};
package.json
:依存パッケージの管理
{
"name": "vba-formatter",
"version": "1.0.0",
"description": "VBA Code Formatter",
"main": "src/index.js",
"scripts": {
"start": "node src/index.js",
"dev": "nodemon src/index.js"
},
"dependencies": {
"@aws-sdk/client-bedrock-runtime": "^3.712.0",
"dotenv": "^16.4.7",
"express": "^4.18.2",
"multer": "^1.4.5-lts.1"
},
"devDependencies": {
"nodemon": "^3.0.2"
}
}
test-connection.js:正しく動作するかテスト
require('dotenv').config();
const { BedrockRuntimeClient, InvokeModelCommand } = require("@aws-sdk/client-bedrock-runtime");
async function testBedrock() {
console.log("環境変数確認:");
console.log("Region:", process.env.AWS_REGION);
console.log("Access Key ID exists:", !!process.env.AWS_ACCESS_KEY_ID);
console.log("Secret Access Key exists:", !!process.env.AWS_SECRET_ACCESS_KEY);
const client = new BedrockRuntimeClient({
region: process.env.AWS_REGION || "us-east-1",
credentials: {
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY
}
});
// モデルごとに適切なリクエスト本文を生成する関数
function getRequestBody(modelId) {
if (modelId.startsWith('amazon.titan')) {
return {
inputText: "Hello",
textGenerationConfig: {
maxTokenCount: 10,
temperature: 0.7,
topP: 1
}
};
} else if (modelId.startsWith('anthropic')) {
return {
anthropic_version: "bedrock-2023-05-31",
max_tokens: 1024,
messages: [
{
role: "user",
content: "Hello, Claude!"
}
]
};
}
}
// テストするモデルのリスト
const modelIds = [
// Titan Models(動作確認済み)
"amazon.titan-text-express-v1",
"amazon.titan-text-lite-v1",
// Mistral Models
"mistral.mistral-7b-instruct-v0:2",
"mistral.mistral-large-2402-v1:0",
"mistral.mistral-large-2407-v1:0",
"mistral.mistral-small-2402-v1:0",
"mistral.mixtral-8x7b-instruct-v0:1",
// Claude Models
"anthropic.claude-3-sonnet-20240229-v1:0", // Claude 3 Sonnet
"anthropic.claude-3-haiku-20240307-v1:0", // Claude 3 Haiku
"anthropic.claude-3-opus-20240229-v1:0", // Claude 3 Opus
"anthropic.claude-3-5-haiku-20241022-v1:0", // Claude 3.5 Haiku
"anthropic.claude-3-5-sonnet-20241022-v2:0",// Claude 3.5 Sonnet v2
"anthropic.claude-3-5-sonnet-20240620-v1:0",// Claude 3.5 Sonnet
"anthropic.claude-v2:1", // Claude 2.1
"anthropic.claude-v2", // Claude 2
"anthropic.claude-instant-v1" // Claude Instant
];
// 各モデルをテスト
for (const modelId of modelIds) {
console.log(`\nテスト: ${modelId}`);
try {
const command = new InvokeModelCommand({
modelId: modelId,
contentType: "application/json",
accept: "application/json",
body: JSON.stringify(getRequestBody(modelId))
});
const response = await client.send(command);
console.log(`✅ ${modelId} は利用可能です`);
// レスポンスの内容を表示
const responseBody = Buffer.from(response.body).toString('utf8');
console.log('応答:', responseBody);
} catch (error) {
console.log(`❌ ${modelId}: ${error.message}`);
}
}
}
testBedrock();
node test-connection.js
### VBA Formatter with Amazon Bedrock プロジェクト説明
#### プロジェクト概要
このプロジェクトはJavaScript言語でNode.jsを利用し、ExpressというWebアプリケーションフレームワークで作成されています。VBAコードを改善するためにAmazon BedrockのAIを活用しています。
#### ファイル構成と役割
1. `src/public/index.html`
- Webブラウザで表示されるユーザーインターフェース
- VBAコードの入力とAI改善結果の表示を担当
2. `src/index.js`
- サーバープログラムのメインファイル
- ブラウザからのリクエストを処理
- AIとの連携を管理
3. `src/bedrock/prompts.js`
- AIへの指示内容(プロンプト)を定義
- コードの改善ルールを記述
4. `src/bedrock/analyzer.js`
- Amazon BedrockのAIと通信
- AIからの応答を処理
#### 処理の流れ
1. ブラウザでVBAコードを入力
2. サーバーがコードを受け取り
3. AIに分析を依頼
4. 改善されたコードが表示
#### 改善点
これまでAWSコンソールのAmazon Bedrockプレイグラウンドでチャット形式で行っていたAIとの対話を、使いやすいWebインターフェースで実現。以下が改善されました:
- コードの入力が簡単に
- AIへの指示内容が明確に表示
- 結果の表示が見やすく
- 複数のコードを連続して処理可能
#### 拡張性
- AIへの指示内容(プロンプト)を簡単に更新可能
- 新しい改善ルールの追加が容易
- チーム固有のコーディングルールも組み込み可能
#### 今後の展開
- より詳細なコード分析機能の追加
- チーム固有のルールの組み込み
- バッチ処理機能の追加
formatter.jsで生成A使用しない処理を使用できていない
このため、ExpressアプリケーションでもNext.jsと同じようにスムーズにデプロイできるのです。
vercel.jsonは、Vercelにデプロイする際の設定ファイルです。
vercel.json
{
"version": 2,
"builds": [
{
"src": "src/index.js",
"use": "@vercel/node"
},
{
"src": "src/public/**",
"use": "@vercel/static"
}
],
"routes": [
{
"src": "/prompts",
"dest": "/src/index.js"
},
{
"src": "/format",
"dest": "/src/index.js"
},
{
"src": "/api/(.*)",
"dest": "/src/index.js"
},
{
"src": "/(.+\\.[a-zA-Z]+)$",
"dest": "/src/public/$1"
},
{
"src": "/(.*)",
"dest": "/src/public/index.html"
}
]
}
.gitignore
node_modules
.env
.env.local
.DS_Store
GitHubにリポジトリを作成
vercel.com にGitHubアカウントでログイン
GitHubリポジトリをインポート
.envの内容を「Environment Variables」設定
Deployで公開完了
リフォーマット版
レンタルサーバーのコントロールパネルに入ります
「基本設定」→「ファイル管理」クリック
該当のドメインを選択→「はじめる」
設定情報で確認できる下記がFTPソフトで接続する際に必要です
SFTPで接続できない場合FTPでする必要があります
「FTPサーバー」が「ホスト名」に該当します
$_SERVER
変数の違いWebサーバー経由での実行で確認(アクセス)することが重要です
Webサーバー経由でアクセスすることで$_SERVER
にはHTTPリクエストの情報が含まれます
対象のコンテナを選択
「Exec」タブから、順番に以下のコマンドを実行
パーミッションの確認と必要に応じて変更
ls -l /var/www/html/server-info.php
chmod 644 /var/www/html/server-info.php
PHPファイルの作成
echo '<?php
header("Content-Type: application/json");
echo json_encode($_SERVER);' > /var/www/html/server-info.php
Webサーバー経由でアクセス
curl localhost/server-info.php
<注意点>
パスが/var/www/html/
でない場合は、プロジェクトの正しいパスに変更してください
dataLayer.push({
'event': 'customEventName', // イベント名
'eventCategory': 'category', // 任意のパラメータ
'eventAction': 'action', // 任意のパラメータ
// その他必要なデータ
});
発火のタイミング:
フォーム送信 → サンクスページ遷移 → 計測
よくある課題
このような通常のフローだと、以下のリスクがあります:
計測のタイミングを前倒し
Google TagManagerの推奨プラクティスの1つより確実にコンバージョンを計測するための、一般的で信頼性の高い方法だと言えます。多くのウェブサイトで採用されている標準的なアプローチです。
フォーム送信 → 【ここで計測】 → サンクスページ遷移
dataLayer.push({
'event': 'virtual_pageview',
// 必要に応じて追加パラメータ
});
トリガーにカスタムイベントを設定
<トリガーの設定>
トリガーのタイプ | カスタムイベント |
イベント名 | virtual_pageview(✓正規表現一致を使用) |
このトリガーの発生場所 | すべてのカスタムイベント |
本日のCV計測に関するMTGについて、参加をご相談させていただきたく存じます。
現在、以下の点について理解を深めたいと考えております: ・各広告媒体でのCV計測の仕組み ・各LPでのコンバージョン実装方法 ・virtual_pageviewの具体的な運用方法 まだ理解が不十分な部分もあり、MTGを通じて学ばせていただければと存じます。
Contents
Amazon Bedrockの活用方法は、コンソールのプレイグラウンドだけではありません。開発環境を構築し、外部APIと連携することで、より高度な機能の実装が可能になります。
AWSマネジメントコンソールにログイン
「Amazon Bedrock」を選択
「Model access」を選択 使用したいモデルの横にあるチェックボックスを選択
左側メニューから「Playground」を選択
AWS Software Development Kit(SDK)は、AWSの各種サービスをプログラムから利用するためのライブラリ群です。AIに限らず、以下のようなAWSサービス全般を扱えます
const { BedrockRuntimeClient } = require("@aws-sdk/client-bedrock-runtime");
import boto3
bedrock = boto3.client('bedrock-runtime')
開発環境からAWSサービスを利用するには、以下の認証情報が必要です:
より高度なアプリケーション開発には、適切な開発環境の構築と、AWS SDKを活用した実装が推奨されます。プレイグラウンドは簡単な実験や検証には適していますが、本格的な開発には開発環境の構築をお勧めします。
環境準備
Node.jsのインストール
AWS CLIのインストール
AWS CLIとは
AWSのサービスをコマンドラインから操作するためのツール
インストール手順はAWS CLI公式インストーラーをダウンロード
AWSクレデンシャルの設定
GitHubからClone
Cloneしたディレクトリでnpm ciを実行
Bootstrap
デプロイ
表示されたUrlにアクセス
Contents
CORSは「異なるオリジン(ドメイン、プロトコル、ポート)間でのリソース共有」を制御するセキュリティの仕組みです。
そのため異なるオリジン間でリソースをリクエストしようとする際にブラウザが制限をかけることにより、CORSエラーが発生します。
異なるオリジン間のリクエストで制限をうけずにアクセスするためには、レスポンスヘッダーに「Access-Control-Allow-Origin」を指定することで可能になります。
異なるオリジン(ドメイン)へリクエストを送る前の「事前確認」
OPTIONS
メソッドを使用して送信されますプリフライトリクエストはブラウザが自動的に行います。開発者が明示的にコードを書く必要はありません。
application/json
などの特殊なContent-Typeを使用する場合ブラウザがOPTIONS
リクエストを送信 サーバーがCORS設定を返答 許可された場合のみ、本来のリクエストを送信
Contents
デジタルイラストの世界では、様々な制作ツールが存在します。今回は、主要な3つのソフトウェアの特徴と違いを詳しく見ていきましょう。
それぞれのツールに特徴があり、一概にどれが最高とは言えません。自分の用途や好みに合わせて選択することをお勧めします。初心者の方はProcreateから始めるのが良いでしょう。プロフェッショナルな用途ではCLIP STUDIO PAINTやAdobe Frescoが適しています。
このブログ記事はいかがでしょうか?各ソフトウェアの特徴や比較点について、さらに詳しく知りたい部分がございましたら、お申し付けください。
はじめに
Webアプリケーション開発において、Cookie・セッション管理の適切な実装は、セキュリティと快適なユーザー体験の両立に不可欠です。本記事では、Chrome開発者ツールを使用した実践的なデバッグ手法と、一般的な実装上の注意点を詳しく解説します。
Contents
Cookieは、ブラウザに保存される小さなテキストデータで、以下のような重要な役割を担っています:
ブラウザのCookieは、ウェブアプリケーションにとって不可欠なデータ保存メカニズムです。ユーザー体験を向上させる多くの機能(ログイン状態の維持、サイト設定の記憶など)の基盤となっています。
Set-Cookie: session_id=abc123; HttpOnly; Secure; SameSite=Strict
重要な設定項目:
Google Chromeの開発者ツールを使ってCookieとセッションの情報を確認する方法を説明します。
開発者ツールで確認できる主要な情報:
// 現在のCookieを表示
console.log(document.cookie);
// Cookie値の取得
function getCookie(name) {
const value = `; ${document.cookie}`;
const parts = value.split(`; ${name}=`);
if (parts.length === 2) return parts.pop().split(';').shift();
}
PHPでのセッション管理例:
session_start();
$_SESSION['user_id'] = $authenticated_user_id;
$_SESSION['last_access'] = time();
開発者ツールを使用した一般的な問題の解決方法:
まとめ
Cookie・セッション管理は、現代のWebアプリケーション開発において非常に重要な要素です。Chrome開発者ツールを活用することで、効率的なデバッグと堅牢な実装が可能になります。セキュリティを意識しながら、ユーザー体験の向上につながる適切な実装を心がけましょう。
この記事では、開発者の実務で直面する具体的な課題に焦点を当て、実践的なソリューションを提供しています。次回は、より高度なセキュリティ対策とパフォーマンス最適化について解説する予定です。
Chromeブラウザの右上にある3点リーダー →「キャスト、保存、共有」→「名前を付けてページを保存」でダウンロードされます。
npm init -y
Bedrock Runtime API のリクエスト作成と送信するために@aws-sdk/client-bedrock-runtimeをインストール
npm install @aws-sdk/client-bedrock-runtime
1)Node.jsライブラリ(データの前処理や基本的な処理)
2)プロンプト:AIに適切な指示を出す
// AWS SDKから必要な機能をインポート
// BedrockRuntimeClient: AWSのBedrockサービスに接続するためのクライアント
// InvokeModelCommand: AIモデルを呼び出すためのコマンド
const { BedrockRuntimeClient, InvokeModelCommand } = require('@aws-sdk/client-bedrock-runtime');
// AWS Bedrockクライアントの設定
// region: AWSのサービスを使用するリージョン(地域)
// credentials: AWS認証情報
const client = new BedrockRuntimeClient({
region: "us-east-1", // 米国東部(バージニア)リージョン
credentials: {
accessKeyId: "あなたのアクセスキー", // AWSアクセスキー
secretAccessKey: "あなたのシークレットキー" // AWSシークレットキー
}
});
// AIに質問するための関数
// async/await: 非同期処理を扱うための構文
async function askAI(question) {
try {
// AIモデルへのリクエスト内容を設定
const input = {
// 使用するAIモデルを指定(Amazon Titan)
modelId: "amazon.titan-text-express-v1",
// データの形式をJSON形式に指定
contentType: "application/json",
accept: "application/json",
// AIモデルへの具体的な指示内容
body: JSON.stringify({
// 質問文を設定
inputText: question,
// AIの応答設定
textGenerationConfig: {
maxTokenCount: 1000, // 最大応答長
temperature: 0.7 // 応答の創造性(0-1: 高いほど創造的)
}
})
};
// AIモデルを呼び出すコマンドを作成
const command = new InvokeModelCommand(input);
// コマンドを実行してAIからの応答を取得
const response = await client.send(command);
// AIからの応答(バイナリ)をテキストに変換
const responseBody = JSON.parse(new TextDecoder().decode(response.body));
// 応答を表示
console.log("回答:", responseBody.results[0].outputText);
} catch (error) {
// エラーが発生した場合の処理
console.error("エラーが発生しました:", error);
}
}
// プログラムのテスト実行
// ここの質問を変更することで、違う質問ができます
askAI("こんにちは!簡単な自己紹介をしてください。");
AWSが提供しているclient.send()
というメソッドが 内部でPromiseを使用していて 私たちはそれをawait
で待っている
IAMユーザーの作成
先ほど作成したapp.jsに取得したキーに更新してファイルを下記の通り実行すると、、
node app.js
回答:
私はAIです。人工知能の一種で、あなたの質問に答えたり、あなたの指示に従ったりすることができます。
私はあなたとチャットできることをうれしく思います。
使っているもの:
async function checkText(text) {
try {
// 1. AIへの指示(プロンプト)を作成
const input = {
modelId: "amazon.titan-text-express-v1", // Amazon TitanのAIモデル
contentType: "application/json",
accept: "application/json",
body: JSON.stringify({
// ここが重要:AIへの指示内容
inputText: `
以下の文章を校閲してください。
- 誤字脱字
- 文法の間違い
- より良い表現の提案
を箇条書きで指摘してください。
文章:${text}`,
textGenerationConfig: {
maxTokenCount: 1000,
temperature: 0.7
}
})
};
// 2. AIに送信して結果を待つ
const command = new InvokeModelCommand(input);
const response = await client.send(command);
// 3. 結果を表示
const responseBody = JSON.parse(new TextDecoder().decode(response.body));
console.log("校閲結果:", responseBody.results[0].outputText);
} catch (error) {
console.error("エラーが発生しました:", error);
}
}
上記のの校閲処理は特別な校閲ロジックは実装していないので、全てAIモデル(プロンプトの指示)に依存しています
より高度なプログラムにするために下記のようにします
// 1. 独自の校閲ルールを追加
const customRules = {
"です/ます": "敬語の統一をチェック",
"こと/もの": "表現の統一をチェック",
// ... その他のルール
};
// 2. 形態素解析ライブラリの使用
// npm install kuromoji
const kuromoji = require('kuromoji');
// 3. 独自の校閲ロジックを実装
async function advancedCheck(text) {
// AIによる校閲
const aiResult = await checkText(text);
// 独自ルールによるチェック
const customResult = applyCustomRules(text);
// 形態素解析による確認
const morphResult = await morphologicalAnalysis(text);
return {
ai: aiResult,
custom: customResult,
morph: morphResult
};
}
// 独自の校閲ルールの実装例
class TextChecker {
constructor() {
// 基本的なルール定義
this.rules = {
// 表記ゆれチェック
styleRules: {
'とっても': 'とても',
'みたい': 'ような',
'すごく': '大変'
},
// 文末表現チェック
endingRules: {
'です・ます': /([てで]います|てます|です)(?![かが])/,
'である': /である(?![かが])/
},
// ビジネス文書チェック
businessRules: {
'不適切': ['思います', 'そうです', 'だと思います'],
'推奨': ['考えられます', '示唆されます', '推察されます']
}
};
}
// テキストをチェックするメソッド
checkText(text) {
const results = [];
// 表記ゆれチェック
for (const [incorrect, correct] of Object.entries(this.rules.styleRules)) {
if (text.includes(incorrect)) {
results.push(`「${incorrect}」は「${correct}」に修正することを推奨します`);
}
}
// その他のルールも同様にチェック
return results;
}
}
// npm install kuromoji
const kuromoji = require('kuromoji');
class MorphologicalAnalyzer {
async initialize() {
return new Promise((resolve, reject) => {
kuromoji.builder({ dicPath: 'node_modules/kuromoji/dict' })
.build((err, tokenizer) => {
if (err) {
reject(err);
} else {
this.tokenizer = tokenizer;
resolve();
}
});
});
}
analyze(text) {
const tokens = this.tokenizer.tokenize(text);
return this.processTokens(tokens);
}
processTokens(tokens) {
const analysis = {
wordTypes: {}, // 品詞の分布
conjugations: [], // 活用の問題
suggestions: [] // 改善提案
};
tokens.forEach(token => {
// 品詞の集計
const pos = token.pos;
analysis.wordTypes[pos] = (analysis.wordTypes[pos] || 0) + 1;
// 活用のチェック
if (token.pos === '動詞' && token.conjugated_form !== '基本形') {
analysis.conjugations.push({
word: token.surface_form,
basic: token.basic_form,
type: token.conjugated_form
});
}
});
return analysis;
}
}
// npm install textlint textlint-rule-preset-japanese
const { TextLintEngine } = require('textlint');
class GrammarChecker {
constructor() {
this.engine = new TextLintEngine({
rules: {
// プリセットルールの使用
'preset-japanese': true,
// カスタムルールの追加
'max-ten': {
// 一文での「、」の数を制限
max: 3
},
'sentence-length': {
// 一文の長さを制限
max: 100
}
}
});
}
async check(text) {
try {
const results = await this.engine.executeOnText(text);
return this.formatResults(results);
} catch (error) {
console.error('文法チェックエラー:', error);
return [];
}
}
formatResults(results) {
return results[0].messages.map(message => ({
line: message.line,
column: message.column,
message: message.message,
ruleId: message.ruleId
}));
}
}
class ComprehensiveTextChecker {
constructor() {
this.customChecker = new TextChecker();
this.morphAnalyzer = new MorphologicalAnalyzer();
this.grammarChecker = new GrammarChecker();
}
async initialize() {
await this.morphAnalyzer.initialize();
}
async checkText(text) {
const results = {
customRules: this.customChecker.checkText(text),
morphology: await this.morphAnalyzer.analyze(text),
grammar: await this.grammarChecker.check(text)
};
// AIの結果と組み合わせる
const aiResults = await checkText(text); // 先ほどのAI機能
results.ai = aiResults;
return this.summarizeResults(results);
}
summarizeResults(results) {
// 結果を整理して返す
return {
suggestions: [
...results.customRules,
...results.grammar.map(g => g.message)
],
analysis: {
wordTypes: results.morphology.wordTypes,
grammarIssues: results.grammar.length,
aiSuggestions: results.ai
}
};
}
}
使用例
async function main() {
const checker = new ComprehensiveTextChecker();
await checker.initialize();
const text = "私はとっても疲れたので、今日は早く帰りたいと思います。";
const results = await checker.checkText(text);
console.log(results);
}
text-checker-project/
│
├── src/ # ソースコードのメインディレクトリ
│ ├── checkers/ # 各チェッカーの実装
│ │ ├── CustomChecker.js # 独自ルールチェッカー
│ │ ├── MorphAnalyzer.js # 形態素解析
│ │ ├── GrammarChecker.js # 文法チェッカー
│ │ └── AIChecker.js # AI利用のチェッカー
│ │
│ ├── rules/ # ルール定義
│ │ ├── styleRules.js # 表記ゆれルール
│ │ ├── grammarRules.js # 文法ルール
│ │ └── businessRules.js # ビジネス文書ルール
│ │
│ ├── utils/ # ユーティリティ関数
│ │ ├── formatter.js # 結果フォーマット
│ │ └── logger.js # ログ処理
│ │
│ └── index.js # メインのエントリーポイント
│
├── config/ # 設定ファイル
│ ├── default.json # デフォルト設定
│ └── textlint-rules.json # textlintルール設定
│
├── tests/ # テストファイル
│ ├── customChecker.test.js
│ ├── morphAnalyzer.test.js
│ └── grammarChecker.test.js
│
├── examples/ # 使用例
│ └── basic-usage.js
│
├── docs/ # ドキュメント
│ ├── API.md
│ └── RULES.md
│
├── node_modules/ # 依存パッケージ
├── package.json
├── package-lock.json
└── README.md
const ComprehensiveTextChecker = require('./checkers/ComprehensiveChecker');
const config = require('../config/default.json');
module.exports = ComprehensiveTextChecker;
const styleRules = require('../rules/styleRules');
const businessRules = require('../rules/businessRules');
class CustomChecker {
constructor(config = {}) {
this.rules = {
style: styleRules,
business: businessRules,
...config.rules
};
}
checkText(text) {
// 実装
}
}
module.exports = CustomChecker;
module.exports = {
// 表記ゆれルール
corrections: {
'とっても': 'とても',
'みたい': 'ような'
},
// その他のルール
};
{
"name": "text-checker",
"version": "1.0.0",
"main": "src/index.js",
"scripts": {
"start": "node src/index.js",
"test": "jest",
"lint": "eslint src/"
},
"dependencies": {
"kuromoji": "^1.0.0",
"textlint": "^12.0.0",
"textlint-rule-preset-japanese": "^7.0.0",
"@aws-sdk/client-bedrock-runtime": "^3.0.0"
},
"devDependencies": {
"jest": "^27.0.0",
"eslint": "^8.0.0"
}
}
const TextChecker = require('../src/index');
async function example() {
const checker = new TextChecker();
await checker.initialize();
const text = "私はとっても疲れたので、今日は早く帰りたいと思います。";
const results = await checker.checkText(text);
console.log(results);
}
example().catch(console.error);
テストとはアプリケーションが正しく動作することを確認する自動チェックです
テスト
トークンベースの認証により、ステートレスなAPI通信が可能
// routes/api.php
// ユーザー登録
Route::post('/register', function (Request $request) {
$user = User::create([
'name' => $request->name,
'email' => $request->email,
'password' => Hash::make($request->password),
]);
$token = $user->createToken('auth-token')->plainTextToken;
return response()->json([
'token' => $token
]);
});
// ログイン
Route::post('/login', function (Request $request) {
$user = User::where('email', $request->email)->first();
if (!$user || !Hash::check($request->password, $user->password)) {
return response()->json(['message' => 'Unauthorized'], 401);
}
$token = $user->createToken('auth-token')->plainTextToken;
return response()->json([
'token' => $token
]);
});
// routes/api.php
Route::middleware('auth:sanctum')->group(function () {
// メモの作成
Route::post('/memos', function (Request $request) {
$memo = $request->user()->memos()->create([
'title' => $request->title,
'content' => $request->content
]);
return response()->json($memo);
});
// メモの取得
Route::get('/memos', function (Request $request) {
$memos = $request->user()->memos;
return response()->json($memos);
});
});
// ログイン
async function login() {
const response = await fetch('api/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
email: 'user@example.com',
password: 'password123'
})
});
const data = await response.json();
// トークンを保存
localStorage.setItem('token', data.token);
}
// メモを作成
async function createMemo() {
const response = await fetch('api/memos', {
method: 'POST',
headers: {
'Authorization': `Bearer ${localStorage.getItem('token')}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
title: '新しいメモ',
content: 'メモの内容'
})
});
const memo = await response.json();
console.log('作成されたメモ:', memo);
}
Contents
Amazon Bedrockは、さまざまなAIモデルを簡単に利用できるようにするAWSのサービスです
モデル | バリアント | 入力料金 (1Kトークン) |
出力料金 (1Kトークン) |
備考 |
---|---|---|---|---|
Claude (Anthropic) | Claude Instant | 0.80円 | 2.40円 | 最も手頃な選択肢 |
Claude (Anthropic) | Claude 3 Sonnet | 3.00円 | 15.00円 | バランスの取れたモデル |
Claude (Anthropic) | Claude 3 Opus | 15.00円 | 75.00円 | 最高性能モデル |
Llama 2 (Meta) | Llama 2 13B | 0.60円 | 0.80円 | オープンソースベース |
Titan (Amazon) | Titan Text Lite | 0.30円 | 0.40円 | 軽量な処理向け |
マネジメントコンソールにログイン
リージョンをバージニア北部にします
→ 使用できるプレイグラウンドに影響
「Amazon Bedrock」を選択
Amazon Bedrock をマネジメントコンソールから利用するには、最初に Base Models(基盤モデル)へのアクセスを設定する必要があります。
「モデルアクセス」
使用するモデルのアクセスを設定
→ 「✅アクセスが付与されました」となります。
「プレイグラウンド」から「Chat/text」を選択
「モデルを選択」
チャットで回答してくれました
「イメージ」で画像生成も可能
CloudShellを起動
Contents
WordPressで固定ページを編集しようと編集をクリックすると上記エラーが発生してしまいました。
PHPのメモリ不足が原因のようです
詳しく言うと、許可されているメモリサイズ(268435456 bytes = 256MB)を使い果たして、
さらに397312 bytes(約388KB)の追加メモリ割りあてようとしたら失敗したようです
おそらく原因は固定ページ内に大量の画像ブロックを配置しているから
メモリの制限は複数の層で設定されています
php.ini
ファイルで設定される制限wp-config.php
ファイルで設定される制限これが最も上位の制限となり、この値を超えることはできない
通常、レンタルサーバーの管理画面やサポートでのみ変更可能
ルードディレクトリに下記の様なphpファイルを設置しアクセスし、memory_limitを確認できます
info.php
<?php phpinfo(); ?>
↓アクセスしてみるとメモリ上限が確認できます
お名前ドットコムをレンタルサーバーで使用している場合は下記の記事を参考できます。
ご利用サーバーのFTPサーバーへご接続いただき、該当ドメインディレクトリ直下に
エラーの内容以上のメモリサイズを記述した「php.ini」ファイルを設置(アップロード)してください。
※既に「php.ini」ファイルを設置されている場合には、同ファイルを修正してください。
ワードプレスのメモリは管理画面のツールのサイトヘルスから確認できます
// wp-config.phpに追加
define('WP_MEMORY_LIMIT', '256M');
define('WP_MAX_MEMORY_LIMIT', '512M');
wp-config.php冒頭の<?phpの次の行に追記しました
↓サイトヘルスで変更が確認できました
今回のエラー
Fatal error: Allowed memory size of 268435456 bytes exhausted (tried to allocate…
はどちらのエラー??
Fatal error: [エラーの説明] in [ファイルのパス] on line [行番号]
はPHPのエラーメッセージの形式
ただし、以下の点に注意が必要です:
このアプローチで編集画面にアクセスできるようになったら、今後のために:
必要に応じてギャラリーの分割 を検討することをお勧めします。
画像サイズの最適化
1ページあたりの画像数の制限
// wp-config.phpに追加
define('WP_DEBUG', true);
define('WP_DEBUG_LOG', true);
遅延読み込みとは: 画面に表示されている部分の画像だけを読み込み、見えていない部分の画像は後回しにする仕組みです。
// functions.php
function add_lazy_loading_to_admin() {
// 管理画面でのみ実行
if (is_admin()) {
// インラインでJavaScriptを追加
wp_add_inline_script('jquery', '
jQuery(document).ready(function($) {
// 画像を監視するための設定
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
if (img.dataset.src) {
img.src = img.dataset.src;
img.removeAttribute("data-src");
}
}
});
});
// 編集画面内の画像を処理
$("#post-body img").each(function() {
const img = $(this);
const originalSrc = img.attr("src");
img.attr("data-src", originalSrc);
img.attr("src", "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7");
observer.observe(img[0]);
});
});
');
}
}
add_action('admin_enqueue_scripts', 'add_lazy_loading_to_admin');
リスクは比較的低いです。理由は:
筆者はこの方法ではメモリ数は抑えることはできませんでした…
データベースクエリとは:
リビジョンとは:
// 必要な項目のみを取得するように変更
function optimize_post_queries() {
add_filter('posts_fields', function($fields) {
global $wpdb;
// 必要最小限のフィールドのみを指定
return "{$wpdb->posts}.ID, {$wpdb->posts}.post_title, {$wpdb->posts}.post_content";
});
}
add_action('init', 'optimize_post_queries');
リスク:高い
WordPressのコア処理に介入するため、エラーが発生すると編集画面が開けなくなる可能性がある
リビジョンのデフォルト設定:
// wp-config.php に追加するだけ
define('WP_POST_REVISIONS', 5); // リビジョンを5個に制限
上記の手順ではリビジョンの設定変更後、既存のリビジョンは自動的には削除されません
筆者の編集画面でのエラーは解消されましたが、画像を削除している最中何回か保存したら下記アクセス制限がかかりました。
Docker Desktopを起動しておく
ターミナルでUbuntuをコマンドライン環境として選択する(理由:本番環境でLinuxを使う、Laravelの定番)
プロジェクト作成コマンド
curl -s "https://laravel.build/laravel-study?with=mysql,mailpit" | bash
Dockerを起動しているのに「Docker is not running.」となってしまったら、下記の通りDocker desktopの設定を変更してみてください
./vendor/bin/sail up -d
① curl:
- ウェブ上からデータをダウンロードするためのツール
- この場合、Laravel Sailのインストールスクリプトを取得している
② -s:
- silentモード(進行状況などを表示しない)
- ダウンロード時の詳細な情報を省略する設定
③ https://laravel.build/:
- Laravel Sailの自動セットアップスクリプトが置かれているURL
④ laravel-study:
- 作成するプロジェクトの名前
- この部分は自由に変更可能(例:my-app, blog-systemなど)
⑤ ?with=mysql,mailpit:
- 一緒にインストールする追加機能の指定
- mysql:データベース
- mailpit:メール送信のテスト環境
⑥ | bash:
- ダウンロードしたスクリプトをすぐに実行する指示
config\app.phpファイル
'timezone' => env('APP_TIMEZONE', 'UTC'),
'locale' => env('APP_LOCALE', 'en'),
env関数
.env( 設定項目名, デフォルト値)
.envファイル
# APP_TIMEZONE=UTC
APP_TIMEZONE=Asia/Tokyo
APP_URL=http://localhost
# APP_LOCALE=en
APP_LOCALE=ja
./vendor/bin/sail artisan make:controller UtilityController
Contents
直接MySQLへの接続はブロックされています(セキュリティ対策)
<?php
header('Content-Type: application/json');
$host = 'mysql80.xxxx.sakura.ne.jp';
$dbname = 'xxxx';
$user = 'xxxx';
$pass = 'xxxx';
try {
$pdo = new PDO("mysql:host=$host;dbname=$dbname", $user, $pass);
$stmt = $pdo->query('SELECT id, email, name FROM users');
$users = $stmt->fetchAll(PDO::FETCH_ASSOC);
echo json_encode([
'success' => true,
'data' => $users
]);
} catch(PDOException $e) {
echo json_encode([
'success' => false,
'error' => $e->getMessage()
]);
}
?>
app/api/users/route.ts
import { NextResponse } from "next/server";
export async function GET() {
try {
const response = await fetch('https://あなたのドメイン/users-api.php');
const data = await response.json();
return NextResponse.json(data);
} catch (error) {
return NextResponse.json({
error: 'ユーザー情報の取得に失敗しました'
}, { status: 500 });
}
}
処理の流れ:
(さくらインターネットのレンタルサーバーでは、標準でMySQLが提供されています)
さくらのコントロールパネルにログイン
「データベース」メニューを選択
「新規作成」をクリック
データベース名、ユーザー名(データベース同一)、パスワードを設定
新規作成したDBを選択し「設定」から「phpMyAdmin」をクリック
.env
ファイルを以下のように設定します
DB_CONNECTION=mysql
DB_HOST=mysql○○.db.sakura.ne.jp # さくらインターネットから提供されるホスト名
DB_PORT=3306
DB_DATABASE=データベース名 # さくらで作成したDB名
DB_USERNAME=ユーザー名 # さくらで設定したユーザー名
DB_PASSWORD=パスワード # さくらで設定したパスワード
Tinker は 対話型コマンドラインツールでphpの実行やデータベースの操作が可能です
php artisan tinker
# DB接続確認(以下のいずれかを実行)
DB::connection()->getPdo(); # 接続成功ならPDOオブジェクトが表示
DB::connection()->getDatabaseName(); # データベース名が表示
# 実際のデータを確認
DB::table('users')->get(); # usersテーブルの全データ表示
マイグレーションファイルは「データベースの設計図」のようなものです。
データベースの構造を定義
database\migrations\0001_01_01_000000_create_users_table.phpを編集
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('users', function (Blueprint $table) {
$table->id();
$table->string('nickname', 100);
$table->string('email')->unique();
$table->string('password');
$table->tinyInteger('locked_flg')->default(0);
$table->integer('error_count')->unsigned()->default(0);
$table->timestamps();
});
}
};
Laravelには「マイグレーション」という機能があり、データベースのテーブル構造やカラムをPHPコードで定義し、コマンドを実行することでデータベースに反映します。
php artisan migrate
app\Models\User.phpを編集
<?php
namespace App\Models;
// use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
class User extends Authenticatable
{
/** @use HasFactory<\Database\Factories\UserFactory> */
use HasFactory, Notifiable;
/**
* The attributes that are mass assignable.
*
* @var array<int, string>
*/
protected $fillable = [
'nickname',
'email',
'password',
'lcoked_flg',
'error_count',
];
/**
* The attributes that should be hidden for serialization.
*
* @var array<int, string>
*/
protected $hidden = [
'password',
];
/**
* Get the attributes that should be cast.
*
* @return array<string, string>
*/
// protected function casts(): array
// {
// return [
// 'email_verified_at' => 'datetime',
// 'password' => 'hashed',
// ];
// }
}
database\factories\UserFactory.phpを編集
<?php
namespace Database\Factories;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str;
/**
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\User>
*/
class UserFactory extends Factory
{
/**
* The current password being used by the factory.
*/
protected static ?string $password;
/**
* Define the model's default state.
*
* @return array<string, mixed>
*/
public function definition(): array
{
return [
'nickname' => fake()->name(), // ランダムな名前を生成
'email' => fake()->unique()->safeEmail(), // ランダムでユニークなメールアドレスを生成
'password' => static::$password ??= Hash::make('password'), // パスワードをハッシュ化
];
}
}
database\seeders\DatabaseSeeder.phpを編集
<?php
namespace Database\Seeders;
use App\Models\User;
// use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
class DatabaseSeeder extends Seeder
{
/**
* Seed the application's database.
*/
public function run(): void
{
User::factory(10)->create();
// User::factory()->create([
// 'name' => 'Test User',
// 'email' => 'test@example.com',
// ]);
}
}
php artisan db:seed
コマンドは、データベースに初期データを挿入するためのコマンドです。
php artisan db:seed
auth/login.blade.phpを作成
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ログイン</title>
<!-- Tailwind CSSを使用 -->
@vite('resources/css/app.css')
</head>
<body class="bg-gray-100">
<div class="min-h-screen flex items-center justify-center">
<div class="bg-white p-8 rounded-lg shadow-md w-96">
<h1 class="text-2xl font-bold mb-8 text-center">ログイン</h1>
@if ($errors->any())
<div class="bg-red-100 text-red-700 p-4 mb-4 rounded">
<ul>
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
<form method="POST" action="{{ route('login') }}">
@csrf
<!-- メールアドレス -->
<div class="mb-4">
<label for="email" class="block text-gray-700 mb-2">メールアドレス</label>
<input
type="email"
name="email"
id="email"
value="{{ old('email') }}"
class="w-full p-2 border rounded focus:outline-none focus:border-blue-500"
required
autofocus
>
</div>
<!-- パスワード -->
<div class="mb-6">
<label for="password" class="block text-gray-700 mb-2">パスワード</label>
<input
type="password"
name="password"
id="password"
class="w-full p-2 border rounded focus:outline-none focus:border-blue-500"
required
>
</div>
<!-- ログインボタン -->
<div class="flex flex-col gap-4">
<button type="submit"
class="w-full py-2 px-4 bg-blue-600 text-white rounded hover:bg-blue-700 focus:outline-none">
ログイン
</button>
</div>
</form>
</div>
</div>
</body>
</html>
ログイン機能のルーティングを設定
routes/web.phpを編集
use App\Http\Controllers\Auth\LoginController;
// ログイン関連のルート
Route::middleware('guest')->group(function () {
// ログイン画面表示
Route::get('/login', [LoginController::class, 'showLoginForm'])
->name('login');
// ログイン処理
Route::post('/login', [LoginController::class, 'login']);
});
// ログアウト(認証済みユーザーのみ)
Route::middleware('auth')->group(function () {
// ダッシュボード表示
Route::get('/dashboard', function () {
return view('dashboard');
})->name('dashboard');
// ログアウト処理
Route::post('/logout', [LoginController::class, 'logout'])
->name('logout');
});
ダウンロードしたインストーラを起動
「Autocomplete Preferences(自動補完設定)」に関する設定画面
「Data Preferences(データ設定)」
初期設定が完了すると、ブラウザが自動的にサインイン画面を開きます。ここでアカウントを作成するか、既存のアカウントでログインします。
最後にCursorアプリケーション内で「Log in」を選択し、ログインを完了させます。
下記サイトより
https://winscp.net/eng/download.php
「DOWNLOAD WINSCP」をクリック
ダウンロードしたインストーラーを実行
自分のみの利用の為、「現在のユーザー用にインストール」
使用許諾を「許諾」
標準的なインストール
インターフェースの設定は後から変更可能です
表示 →環境設定
インストール
完了
ssh接続を初回実行時、下記の警告が出ます
承認すると、再度パスワード入力を求められますので、実施
ディレクトリ同期移動
有効化すると左右同じディレクトリ階層で表示されます
任意のファイルを「右クリック」→「編集」→「設定」に進みます
エディタから外部エディタで参照でVSCodeのexeファイルを選択
追加されたCode(VSCode)を上にをクリックし一番上に移動します
メニューバーの歯車(環境設定)からパネルを選択し、隠しファイルを表示するを✓
WinSCPとは?インストール方法や使い方を解説する【初心者向け】
https://miyashimo-studio.jp/blog/detail/winscp-how-to-use/
こんにちは!今回は、Docker環境でのSSL接続設定について詳しく解説します。SSL(Secure Sockets Layer)は、インターネット上での通信を暗号化し、セキュリティを向上させる重要な技術です。Dockerを使用した開発環境でも、SSL接続を適切に設定することで、より本番環境に近い安全な環境を構築できます。
Contents
警告ページで「詳細」や「続行」などのオプションを探します。 リスクを理解した上で、「Webサイトへ進む」などのオプションを選択します。 ブラウザは警告を表示しつつも、ページの表示を許可します。
注意点:この方法は開発環境でのみ推奨されます。
まず、docker-compose.yml
ファイルでSSL接続用のポートを公開する必要があります。以下のように設定します:
services:
app:
ports:
- "0.0.0.0:8082:80"
- "443:443"
この設定により、ホストマシンのポート443がDockerコンテナ内のポート443にマッピングされます。これにより、外部からのHTTPS接続(ポート443)をコンテナ内のApacheサーバーで受け付けることができます。
次に、Dockerfileで以下のようなSSL関連の設定を行います:
1)SSLモジュールの有効化:
RUN a2enmod ssl
2)デフォルトのSSLサイト設定の有効化:
RUN a2ensite default-ssl
3)自己署名SSL証明書の生成:
RUN echo '#!/bin/bash
openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
-keyout /etc/ssl/private/apache-selfsigned.key \
-out /etc/ssl/certs/apache-selfsigned.crt \
-subj "/CN=localhost"
sed -i "s|SSLCertificateFile.*|SSLCertificateFile /etc/ssl/certs/apache-selfsigned.crt|" /etc/apache2/sites-available/default-ssl.conf
sed -i "s|SSLCertificateKeyFile.*|SSLCertificateKeyFile /etc/ssl/private/apache-selfsigned.key|" /etc/apache2/sites-available/default-ssl.conf' > /usr/local/bin/generate_ssl_cert.sh
RUN chmod +x /usr/local/bin/generate_ssl_cert.sh && /usr/local/bin/generate_ssl_cert.sh
DockerfileのRUN
コマンドがデフォルトでroot権限で実行されるため、追加の権限設定なしでopenssl
コマンドを実行できます。複雑な権限設定なしで、システムレベルの操作を容易に行えます。
openssl
コマンドを使用して自己署名証明書を生成しています。 -x509
:自己署名証明書を生成することを指定します。 -nodes
:秘密鍵をパスワードで保護しないことを指定します。 -days 365
:証明書の有効期間を1年に設定します。 -newkey rsa:2048
:2048ビットのRSA鍵を新しく生成します。 -keyout
と-out
:鍵と証明書の出力先を指定します。 -subj "/CN=localhost"
:証明書のサブジェクト(ここではCommon Name)を設定します。…証明書が保護するドメイン名または IP アドレスを指定しますs|旧パターン|新パターン|
の形式で、|
はデリミタ(区切り文字)として機能4)ポート443の公開:
EXPOSE 80 443
ローカルDocker開発環境では難しい、、、?
mkcertを使用することで、自己署名証明書の警告なしに安全な開発環境を構築できます。
1)Windows PowerShell”を右クリックし、“管理者として実行”を選択します。
2)Chocolateyがインストールされていない場合は、まずChocolateyをインストール
Chocolateyは、Windowsのためのパッケージマネージャーです。
Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; iex ((New-Object System.Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1'))
3)mkcertをインストール:
choco install mkcert
インストールの確認
mkcert --version
4)ローカル認証局のインストール
mkcert -install
インストールの確認
mkcert -CAROOT
5)証明書の生成:
プロジェクトディレクトリに移動し、以下のコマンドを実行:
mkdir certs
cd certs
mkcert localhost 127.0.0.1 ::1
→ファイル生成
・証明書ファイル: [最初のドメイン名]+[追加エントリ数].pem
(公開鍵を含む)
・秘密鍵ファイル: [最初のドメイン名]+[追加エントリ数]-key.pem
6)各ファイルを編集
鍵ファイルを作成したら下記を実施します
project_root/
│
├── local-mysite/
│ ├── certs/
│ │ ├── localhost+2.pem
│ │ └── localhost+2-key.pem
│ ├── env_vars/
│ │ └── localhost
│ ├── sites-available/
│ │ └── 000-default.conf
│ ├── .htaccess
│ ├── docker-compose.yml
│ ├── Dockerfile
│ ├── php.ini
│ └── ssl_cert_gen.sh
│
└── mysite/
└── (PHPアプリケーションファイル)
▼docker-compose.yml
version: "3.4"
services:
app:
# Dockerfileからイメージをビルド
build:
context: .
dockerfile: Dockerfile
# ボリュームマウントの設定
volumes:
# 環境変数ファイルをマウント
- ./env_vars:/var/www/env_vars
# PHPアプリケーションのソースコードをマウント
- ../0824_mysite:/var/www/html
# SSL証明書をマウント(読み取り専用)
- ./certs/localhost+2.pem:/etc/ssl/certs/localhost+2.pem:ro
# SSL秘密鍵をマウント(読み取り専用)
- ./certs/localhost+2-key.pem:/etc/ssl/private/localhost+2-key.pem:ro
# Apache設定ファイルをマウント
- ./sites-available:/etc/apache2/sites-available
# ポートマッピングの設定
ports:
# HTTP: ホストの8082ポートをコンテナの80ポートにマッピング
- "0.0.0.0:8082:80"
# HTTPS: ホストの443ポートをコンテナの443ポートにマッピング
- "443:443"
# 環境変数の設定(必要に応じて追加)
environment:
- APACHE_DOCUMENT_ROOT=/var/www/html
▼Dockerfile
# SSL対応PHP開発環境のDockerfile
# PHP 8.1とApacheをベースとした公式イメージを使用
FROM php:8.1-apache
# カスタムApache設定ファイルをコンテナ内の指定された場所にコピー
COPY ./sites-available/000-default.conf /etc/apache2/sites-available/000-default.conf
# PDO_MySQL拡張をインストールし、Apacheのrewrite、proxy、proxy_httpモジュールを有効化
RUN docker-php-ext-install pdo_mysql && a2enmod rewrite proxy proxy_http
# SSLモジュールを有効化
RUN a2enmod ssl
# デフォルトのSSLサイト設定を有効化
RUN a2ensite default-ssl
# 必要なパッケージをインストール
RUN apt-get update && apt-get install -y \
curl \
gnupg \
git \
unzip \
openssl \
# Node.jsのセットアップスクリプトを取得して実行
&& curl -sL https://deb.nodesource.com/setup_18.x | bash - \
# Node.jsをインストール
&& apt-get install -y nodejs \
# パッケージリストを削除してイメージサイズを削減
&& rm -rf /var/lib/apt/lists/*
# Composerをコンテナ内にコピー
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
# コンテナ内の作業ディレクトリを設定
WORKDIR /var/www/html
# Composerの設定
ENV COMPOSER_HOME /var/www/.composer
RUN mkdir -p $COMPOSER_HOME && chown -R www-data:www-data $COMPOSER_HOME
# Apache実行ユーザーの設定
ENV APACHE_RUN_USER www-data
ENV APACHE_RUN_GROUP www-data
# Apacheの設定を変更し、.htaccessが機能するようにAllowOverrideをAllに設定
RUN sed -i '/<Directory \/var\/www\/>/,/<\/Directory>/ s/AllowOverride None/AllowOverride All/' /etc/apache2/apache2.conf
# コンテナのポート80(HTTP)と443(HTTPS)を公開
EXPOSE 80 443
# .htaccessファイルをコンテナ内にコピー
COPY .htaccess /var/www/html/.htaccess
# .htaccessファイルの所有権と権限を設定
RUN chown www-data:www-data /var/www/html/.htaccess && chmod 644 /var/www/html/.htaccess
# カスタムphp.iniをコンテナ内にコピー
COPY ./php.ini /usr/local/etc/php/php.ini
# PHPエラーログファイルを作成し、適切な権限を設定
RUN touch /var/log/php_errors.log && chmod 666 /var/log/php_errors.log
# mkcertで生成した証明書をコンテナ内にコピー
COPY ./certs/localhost+2.pem /etc/ssl/certs/localhost.crt
COPY ./certs/localhost+2-key.pem /etc/ssl/private/localhost.key
# 証明書のパーミッションを適切に設定
RUN chmod 644 /etc/ssl/certs/localhost.crt && \
chmod 600 /etc/ssl/private/localhost.key
# Apacheをフォアグラウンドで実行
CMD ["apache2-foreground"]
▼000-default.conf
<VirtualHost *:80>
ServerName localhost
DocumentRoot /var/www/html
Redirect permanent / https://localhost/
# HTTPのアクセスログとエラーログ(必要に応じてコメントを外す)
# ErrorLog ${APACHE_LOG_DIR}/error.log
# CustomLog ${APACHE_LOG_DIR}/access.log combined
</VirtualHost>
<VirtualHost *:443>
ServerName localhost
DocumentRoot /var/www/html
SSLEngine on
SSLCertificateFile /etc/ssl/certs/localhost+2.pem
SSLCertificateKeyFile /etc/ssl/private/localhost+2-key.pem
<Directory /var/www/html>
AllowOverride All
Require all granted
</Directory>
ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined
</VirtualHost>
```
この設定は、HTTPからHTTPSへのリダイレクトと、SSL/TLS接続の基本的な設定を提供します。
mkcertとopenssl、Let’s Encryptを使用したSSL設定の主な違いは、使いやすさと生成される証明書の性質にあります。
mkcertは開発環境に特化しており、簡単な操作で信頼されたSSL証明書を生成できます。一方、opensslはより汎用的で、詳細な設定が可能ですが、使用にはより深い知識が必要です。
開発環境では、mkcertの使用が推奨されます:
一方、以下の場合はopensslの使用が適切です:
重要なポイントは、開発環境と本番環境で異なるアプローチを取ることが多いということです。開発ではmkcertの簡便性を活かし、本番ではopensslやLet’s Encryptなどを使用して、より厳格なセキュリティを確保することが一般的です。
mkcertはローカル環境に最適化されているため、本番環境特有の問題を見逃す可能性がある
project-dir
├dockerfile
└docker-compose.yml
Dockerfileに記述された指示に従って、Dockerイメージ(設計図)を作成します
▽Dockerfile
# PHP 7.4 と Apache がインストールされた公式イメージを使用
FROM php:7.4-apache
# システムの更新とZIP関連のライブラリをインストール
# libzip-dev: ZIPファイルを扱うためのライブラリ
RUN apt-get update && apt-get install -y libzip-dev
# PHPにZIP拡張機能をインストール
# これにより、PHPでZIPファイルの操作が可能になります
RUN docker-php-ext-install zip
# Composerのインストール
# Composerは、PHPの依存関係管理ツールです
RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
version: '3'
services:
web:
build: .
ports:
- "8080:80"
volumes:
- ./src:/var/www/html
「docker-compose build
」「docker-compose up -d
」docker-compose build
:
docker-compose up -d
:
src
)が存在しない場合、Docker は自動的にそれを作成します。project_root/
├── Dockerfile
├── docker-compose.yml
└── src/
├── index.php
└── send_mail.php
▽index.php
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>PHPMailerテスト</title>
</head>
<body>
<h1>PHPMailerテスト</h1>
<form action="send_mail.php" method="post">
<label for="to">宛先:</label>
<input type="email" id="to" name="to" required><br><br>
<label for="subject">件名:</label>
<input type="text" id="subject" name="subject" required><br><br>
<label for="message">本文:</label><br>
<textarea id="message" name="message" rows="4" cols="50" required></textarea><br><br>
<input type="submit" value="送信">
</form>
</body>
</html>
▽send_mail.php
<?php
use PHPMailer\PHPMailer\PHPMailer;// PHPMailerライブラリの読み込み
use PHPMailer\PHPMailer\Exception;// PHPMailerライブラリの読み込み
require 'vendor/autoload.php';// PHPMailerライブラリの読み込み
if ($_SERVER["REQUEST_METHOD"] == "POST") {
$mail = new PHPMailer(true); //PHPMailerのインスタンス作成(trueは例外を有効にする)
try {
//SMTPサーバー:Gmailの設定
$mail->isSMTP();
$mail->Host = 'smtp.gmail.com'; // SMTPサーバーを指定
$mail->SMTPAuth = true;
$mail->Username = 'yourmail@gmail.com'; // SMTPユーザー
$mail->Password = 'your app pass'; // SMTPパスワード
$mail->SMTPSecure = PHPMailer::ENCRYPTION_SMTPS;
$mail->Port = 465;
//送信元、送信先の設定
$mail->setFrom('yourmail@gmail.com', 'Mailer');
$mail->addAddress($_POST['to']);
//メール本文
$mail->isHTML(true);
$mail->Subject = $_POST['subject'];
$mail->Body = $_POST['message'];
$mail->send();
echo 'メッセージが送信されました';
} catch (Exception $e) {
echo "メッセージを送信できませんでした。Mailer Error: {$mail->ErrorInfo}";
}
}
docker-compose exec web composer require phpmailer/phpmailer
Google→セキュリティ→2段階認証プロセス→アプリパスワードから設定
send_mail.phpを編集
注意)本番環境では、これらの設定を環境変数や別の設定ファイルに移動し、Gitなどのバージョン管理システムにコミットしないようにすることをおすすめします。
サニタイズ(sanitize)とは、ユーザーから入力されたデータを安全で使用可能な形式に変換することを指します。
セキュリティを強化するために使用される特別なHTTPヘッダ
▽send_mail.php
<?php
// ob_start(); は、「出力を一時的に裏側で溜めておく」という命令
// もし途中でエラーメッセージが表示されたり、予期せぬ出力があったりすると、セキュリティヘッダーを設定できなくなるため
ob_start();
// セッションが開始されていない場合のみ、設定を変更してセッションを開始
if (session_status() == PHP_SESSION_NONE) { // セッション関連の設定
ini_set('session.cookie_httponly', 1);
ini_set('session.cookie_secure', 1); // セッションを開始
session_start();
} else {
// セッションが既に開始されている場合は、そのまま続行
session_start();
}
// CSRFトークンがセッションに存在しない場合は生成
if (!isset($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
// セキュリティヘッダーの設定
header("X-XSS-Protection: 1; mode=block");
header("X-Frame-Options: SAMEORIGIN");
header("X-Content-Type-Options: nosniff");
header("Referrer-Policy: strict-origin-when-cross-origin");
header("Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline';");
use PHPMailer\PHPMailer\PHPMailer;// PHPMailerライブラリの読み込み
use PHPMailer\PHPMailer\Exception;// PHPMailerライブラリの読み込み
require 'vendor/autoload.php';// PHPMailerライブラリの読み込み
if ($_SERVER["REQUEST_METHOD"] == "POST") {
// CSRFトークンの検証
if (!isset($_POST['csrf_token']) || !hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'])) {
// エラーをログに記録
error_log("CSRF token mismatch. POST token: " . ($_POST['csrf_token'] ?? 'not set') . ", Session token: " . ($_SESSION['csrf_token'] ?? 'not set'));
die('セッションが期限切れか無効です。ページを更新して再度お試しください。');
}
// 入力のサニタイズとバリデーション
$to = filter_var($_POST['to'], FILTER_SANITIZE_EMAIL);
$subject = htmlspecialchars($_POST['subject'], ENT_QUOTES, 'UTF-8');
$message = htmlspecialchars($_POST['message'], ENT_QUOTES, 'UTF-8');
// メールアドレスの妥当性チェック
if (!filter_var($to, FILTER_VALIDATE_EMAIL)) {
die('無効なメールアドレスです。');
}
$mail = new PHPMailer(true); //PHPMailerのインスタンス作成(trueは例外を有効にする)
try {
//SMTPサーバー:Gmailの設定
$mail->isSMTP();
$mail->Host = 'smtp.gmail.com'; // SMTPサーバーを指定
$mail->SMTPAuth = true;
$mail->Username = 'yourmail@gmail.com'; // SMTPユーザー
$mail->Password = 'your app pass'; // SMTPパスワード
$mail->SMTPSecure = PHPMailer::ENCRYPTION_SMTPS;
$mail->Port = 465;
//送信元、送信先の設定
$mail->setFrom('yourmail@gmail.com', 'Mailer');
$mail->addAddress($to);
//メール本文
$mail->Subject = $subject;
$mail->Body = $message;
$mail->AltBody = strip_tags($message); //HTMLタグを除去
$mail->send();
echo 'メッセージが送信されました';
} catch (Exception $e) {
// 詳細なエラー情報を隠し、ログに記録します。
error_log("メール送信エラー: " . $mail->ErrorInfo);
echo "メッセージを送信できませんでした。管理者にお問い合わせください。";
}
// 新しいCSRFトークンの生成
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
} else {
// POSTメソッド以外でアクセスされた場合の処理
die('不正なアクセスです。');
}
// 出力バッファリングを終了し、出力を送信
ob_end_flush();
▽index.php
<?php
session_start();
if (!isset($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
} // CSRFトークンがセッションに存在しない場合は生成
?>
config.php
を作成しますproject-dir\config\config.php
<?php
define('SMTP_HOST', getenv('SMTP_HOST') ?: 'smtp.example.com');
define('SMTP_USER', getenv('SMTP_USER') ?: 'user@example.com');
define('SMTP_PASS', getenv('SMTP_PASS') ?: 'password');
▽send_mail.php
…
require 'vendor/autoload.php';// PHPMailerライブラリの読み込み
require_once __DIR__ . '/../config/config.php';
…
//SMTPサーバー:Gmailの設定
$mail->isSMTP();
$mail->Host = SMTP_HOST; // SMTPサーバーを指定
$mail->Username = SMTP_USER; // SMTPユーザー
$mail->Password = SMTP_PASS; // SMTPパスワード
$mail->SMTPAuth = true;
$mail->SMTPSecure = PHPMailer::ENCRYPTION_SMTPS;
$mail->Port = 465;
▽docker-compose.yml
volumes:
- ./src:/var/www/html
- ./config:/var/www/config.php
docker-compose down
docker-compose build
docker-compose up -d
.gitignore
ファイルを作成▽.gitignore
/config/config.php
▽config.php
<?php
// 環境の判別
$is_local = ($_SERVER['SERVER_NAME'] == 'localhost' || $_SERVER['SERVER_ADDR'] == '127.0.0.1');
// デバッグモードの設定
define('DEBUG_MODE', $is_local); // ローカルではデバッグモードON、本番では OFF
// 本番環境で一時的にデバッグモードを有効化する場合
// define('DEBUG_MODE', true); // コメントを外して使用
if ($is_local) {
// ローカル環境(Docker)の設定
define('SMTP_HOST', getenv('SMTP_HOST') ?: 'smtp.gmail.com');
define('SMTP_USER', getenv('SMTP_USER') ?: 'xxxx@gmail.com');
define('SMTP_PASS', getenv('SMTP_PASS') ?: 'xxxx');
define('SMTP_PORT', 587);
define('SMTP_SECURE', 'tls');
} else {
// さくらインターネット環境の設定
define('SMTP_HOST', 'xxxx');
define('SMTP_USER', 'xxxx');
define('SMTP_PASS', 'xxxx');
define('SMTP_PORT', 587);
define('SMTP_SECURE', 'tls');
}
// サイトの URL
define('SITE_URL', $is_local ? 'http://localhost:8080' : 'xxxx');
下記のホスト、ユーザ名、パスワードについて
さくらのメールボックスを PHPMailer で SMTP + STARTTLS で送信する時の注意点https://qiita.com/ameyamashiro/items/c7283bd1ec5dd3146ef9
▽send_mail.php
<?php
// ob_start(); は、「出力を一時的に裏側で溜めておく」という命令
// もし途中でエラーメッセージが表示されたり、予期せぬ出力があったりすると、セキュリティヘッダーを設定できなくなるため
ob_start();
// 設定ファイルの読み込み
if (strpos(__DIR__, '/home/siennahare23') !== false) {
// さくらインターネット環境
require_once '/home/siennahare23/config/config.php';
} else {
// ローカル環境
require_once __DIR__ . '/../config/config.php';
}
// セッションが開始されていない場合のみ、設定を変更してセッションを開始
if (session_status() == PHP_SESSION_NONE) {
// セッション関連の設定
ini_set('session.cookie_httponly', 1);
ini_set('session.cookie_secure', 1);
// セッションを開始
session_start();
} else {
// セッションが既に開始されている場合は、そのまま続行
session_start();
}
// CSRFトークンがセッションに存在しない場合は生成
if (!isset($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
// セキュリティヘッダーの設定
header("X-XSS-Protection: 1; mode=block"); //ブラウザの組み込みのXSS対策フィルターを有効にし、攻撃を検出したら、ページの読み込みをブロックします。
header("X-Frame-Options: SAMEORIGIN"); //ページを<frame>、<iframe>、<embed>、<object>で表示することを許可します。ただし、同じオリジンの場合のみ。(クリックジャッキング対策)
header("X-Content-Type-Options: nosniff"); //ブラウザがコンテンツタイプをスニッフィングしないようにします。
header("Referrer-Policy: strict-origin-when-cross-origin"); //クロスオリジンのリクエストに対しては、Referer ヘッダーにはリクエスト元のオリジンのみを含めます。
header("Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline';"); //様々な攻撃(XSS、データ注入など)からサイトを保護します。
use PHPMailer\PHPMailer\PHPMailer;// PHPMailerライブラリの読み込み
use PHPMailer\PHPMailer\Exception;// PHPMailerライブラリの読み込み
require 'vendor/autoload.php';// PHPMailerライブラリの読み込み
if ($_SERVER["REQUEST_METHOD"] == "POST") {
// CSRFトークンの検証
if (!isset($_POST['csrf_token']) || !hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'])) {
// エラーをログに記録
error_log("CSRF token mismatch. POST token: " . ($_POST['csrf_token'] ?? 'not set') . ", Session token: " . ($_SESSION['csrf_token'] ?? 'not set'));
die('セッションが期限切れか無効です。ページを更新して再度お試しください。');
}
// 入力のサニタイズとバリデーション
$to = filter_var($_POST['to'], FILTER_SANITIZE_EMAIL);
$subject = htmlspecialchars($_POST['subject'], ENT_QUOTES, 'UTF-8');
$message = htmlspecialchars($_POST['message'], ENT_QUOTES, 'UTF-8');
// メールアドレスの妥当性チェック
if (!filter_var($to, FILTER_VALIDATE_EMAIL)) {
die('無効なメールアドレスです。');
}
$mail = new PHPMailer(true); //PHPMailerのインスタンス作成(trueは例外を有効にする)
// デバッグモードの設定
// $mail->SMTPDebug = 3; // デバッグ出力を有効化
$mail->Debugoutput = 'html'; // デバッグ出力形式をHTMLに設定
try {
//SMTPサーバー:Gmailの設定
$mail->isSMTP();
$mail->Host = SMTP_HOST; // SMTPサーバーを指定
$mail->Username = SMTP_USER; // SMTPユーザー
$mail->Password = SMTP_PASS; // SMTPパスワード
$mail->SMTPAuth = true;
$mail->SMTPSecure = SMTP_SECURE;
$mail->Port = SMTP_PORT;
//送信元、送信先の設定
$mail->setFrom(SMTP_USER, 'Mailer');
$mail->addAddress($to);
//メール本文
$mail->isHTML(true);
$mail->Subject = $subject;
$mail->Body = $message;
$mail->AltBody = strip_tags($message); //HTMLタグを除去
$mail->send();
echo 'メッセージが送信されました';
} catch (Exception $e) {
if (DEBUG_MODE) {
echo "Message could not be sent. Mailer Error: {$mail->ErrorInfo}<br>";
echo "Detailed error: " . $e->getMessage();
} else {
echo "メッセージを送信できませんでした。管理者にお問い合わせください。";
}
error_log("メール送信エラー: " . $mail->ErrorInfo);
}
// 新しいCSRFトークンの生成
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
} else {
// POSTメソッド以外でアクセスされた場合の処理
die('不正なアクセスです。');
}
// 出力バッファリングを終了し、出力を送信
ob_end_flush();
<ローカルのディレクトリ構成>
project_root/
│
├── src/
│ ├── index.php
│ ├── send_mail.php
│ └── vendor/ ...
├── config/
│ └── config.php
│
└── docker-compose.yml
<本番環境のディレクトリ構成>
/home/xxxx/www/xxxx/
│
├── contact/
│ │
│ ├── index.php
│ ├── send_mail.php
│ └── vendor/ ...
│
└── config/
└── config.php
注意)デバッグモードの無効化
Contents
本手順はパスワード認証方式
パスワード認証方式 | 公開鍵認証方式 | |
---|---|---|
手段 | パスワード | 「公開鍵」など生成したファイル |
安全度 | 低い パスワード流出で第3者にログインされる恐れがあります | 高い |
作業について | 簡単 | 複雑 |
C:\Users\jingt>ssh shiennahare23@siennahare23.sakura.ne.jp
The authenticity of host 'siennahare23.sakura.ne.jp (163.43.87.150)' can't be established.
ED25519 key fingerprint is SHA256:6UflOXoaum1XWseZ2Xj8SYFcGI7lnEw1p42zjlc/Egk.
This key is not known by any other names
Are you sure you want to continue connecting (yes/no/[fingerprint])?
このメッセージは、SSHを使用して初めて接続しようとしたときに表示されるものです。サーバーのED25519公開鍵のフィンガープリントが表示され、このサーバーが信頼できるものかどうかを確認しています。
→接続先サーバに設定された公開鍵のフィンガープリントが表示されるので、問題がなければ”yes”を入力します。
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added 'siennahare23.sakura.ne.jp' (ED25519) to the list of known hosts.
Connection closed by 163.43.87.150 port 22
siennahare23@siennahare23.sakura.ne.jp's password:
パスワードを要求されるのでパスワードを入力
サーバーパスワードの確認:
https://help.sakura.ad.jp/rs/2243
Welcome to FreeBSD!
→と表示されログインできます
% exit
logout
Connection to siennahare23.sakura.ne.jp closed.
% bash
[siennahare23@www3910 ~]$
※bashに切り替える理由は分かりません…
[siennahare23@www3910 ~]$ cd ~/.ssh
[siennahare23@www3910 ~/.ssh]$ ssh-keygen -t rsa -b 4096
鍵の名前、パスフレーズを要求されますが、「Enter」で進めるとデフォルトの名前(「id_rsa」、「id_rsa.pub」)でパスフレーズを必要としない鍵のペアが生成されます
Generating public/private rsa key pair.en -t rsa -b 4096
Enter file in which to save the key (/home/siennahare23/.ssh/id_rsa):
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /home/siennahare23/.ssh/id_rsa.
Your public key has been saved in /home/siennahare23/.ssh/id_rsa.pub.
The key fingerprint is:
SHA256:3x+egZ5oS58YhHEoWZbiDnzFg+00qH2/y/Da9G1olgQ siennahare23@www3910.sakura.ne.jp
The key's randomart image is:
+---[RSA 4096]----+
| =o. |
| ++O. |
| . +o=oo. |
| + +.o+E |
| + .S... |
| . o.... |
| . =oooo |
| BoB=*.+ |
| .oO=*.= |
+----[SHA256]-----+
これで、SSH接続やGitHubとの認証に使用できる鍵ペアが正常に生成されました。
公開鍵の内容を表示し、全文コピーします
cat ~/.ssh/id_rsa.pub
Githubの「Settings」→「SSH and GPG keys」→「New SSH key」をクリック
コピーした公開鍵の内容をはりつけ
[siennahare23@www3910 ~/.ssh]$ touch ~/.ssh/config
▽config
Host github.com
HostName github.com
IdentityFile ~/.ssh/id_rsa
User git
※秘密鍵の名前を変更している場合はデフォルトの「id_rsa」から変更する必要があります
[siennahare23@www3910 ~]$ ssh -T git@github.com
↓
SSH 鍵が正しく設定され、GitHub に登録されている公開鍵と一致したことを示しています。
Hi ida240609! You've successfully authenticated, but GitHub does not provide shell access.
“but GitHub does not provide shell access.” – これは通常のメッセージです。GitHub は直接のシェルアクセスを提供していないことを説明しています。つまり、対話的なシェルセッションは使用できませんが、Git 操作(push, pull など)には問題なく SSH を使用できます。
これで、SSH を使用して GitHub リポジトリのクローン、プッシュ、プルなどの操作を行うことができます。
例)
git clone git@github.com:username/repository.git
のようなコマンドが使用可能です(username と repository は実際のものに置き換えてください)。
Contents
VSCodeと連携可能(その他エディターも)
GitHub Copilot Individual サブスクリプションは、月単位または年単位のサイクルで利用できます。
GitHub Copilot の課金
https://docs.github.com/ja/billing/managing-billing-for-github-copilot/about-billing-for-github-copilot
1)プロフィールアイコンをクリック、メニューから「Settings」をクリック
2)「Copilot」をクリック、「Start free trial」をクリック
3)プランを選択し、「Get access to GitHub Copilot」をクリック
4)名前、住所を入力し、「Save」
5)クレジットカード等支払い方法情報を入力
6)登録情報を確認後「Submit」をクリック
Select Your preferencesで好みの設定
以上で完了です
解約方法もGithubのページ、ユーザアイコンをクリックし設定より可能です
RubyInstallerのダウンロードページ
https://rubyinstaller.org/downloads
PCのビット数を確認し、インストーラーを選択
▽設定→システム→バージョン情報より確認できます
1)「Install for me only」をクリック
2)同意して「Next」をクリック
3)下記の設定(デフォルトのまま)「Install」をクリック
4)下記の設定(デフォルトのまま)「Next」をクリック
→インストールが開始されます
5)数分後、下記画面が表示されインストール終了
→「Finish」をクリックするとインストーラーの画面は消えてターミナルが表示されます
「MSYS2」… Ruby開発で便利なツール
「1」、「Enter」を押下するとインストールが開始されます
続いて「2」、「Enter」を押下、最新版にアップデート
最後に「3」、「Enter」を押下、3つ目のメニュー実行
完了したら「Enter」をクリックして「MSYS2」のインストール完了
ターミナルにてバージョンを確認→表示されれば正しくインストールされています
C:\Users\xxx>ruby -v
ruby 3.3.3 (2024-06-12 revision f1c7b6f435) [x64-mingw-ucrt]
Contents
npx create-react-app app-dir
VS Codeの場合「Branchの発行」をクリックしリポジトリ名を入力する
npm run build
→buildディレクトリが作成されます
Reactアプリを簡単にデプロイできる「gh-pages」ツールをインストール
npm install --save gh-pages
▽package.jsonにて「homepageフィールド」の追加、「デプロイスクリプト」の追加
{
"homepage": "https://<GitHubアカウント名>.github.io/<GitHubリポジトリ名>/",
"name": "app-dir",
"version": "0.1.0",
"private": true,
"dependencies": {
"@testing-library/jest-dom": "^5.17.0",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-scripts": "5.0.1",
"web-vitals": "^2.1.4"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"predeploy": "npm run build",
"deploy": "gh-pages -d build",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}
cd app-dir
npm run deploy
"homepage": "https://github0612.github.io/app-dir0612/",
「Branchの発行」後に作成されるリポジトリにてGitHubでBranchを「Save」をクリックすると数分後URLが表示されます
▽数分後URLが表示されます
▽サイトにて表示確認できます
GitHub Pages でReact Appを公開する方法
https://note.com/wecken/n/n73196eb22a51
ReactアプリをGithub Pagesにデプロイする方法
https://qiita.com/snow_swallow/items/8455dd135b81fe0ce25f
functions.phpを作成し、編集していたところエラー発生。
「functions.php」の1行目のコメントアウトを削除したところ解消しました。
<!-- カスタムフィールドの値をタイトルに自動で反映 -->
<?php
add_action('acf/save_post', 'replace_post_title');
function replace_post_title( $post_id ) {
// タイトルの入ったフィールドを取得
$newtitle = get_field("news_text",$post_id);
//タイトルが空でない場合は更新
if( $newtitle ) {
$args = array(
"ID"=>$post_id,
"post_title" => $newtitle
);
wp_update_post($args);
}
}
▽1行目のコメントアウトを削除
<?php
add_action('acf/save_post', 'replace_post_title');
function replace_post_title( $post_id ) {
// タイトルの入ったフィールドを取得
$newtitle = get_field("news_text",$post_id);
//タイトルが空でない場合は更新
if( $newtitle ) {
$args = array(
"ID"=>$post_id,
"post_title" => $newtitle
);
wp_update_post($args);
}
}
PHP7.3からPHP8.0に変更がありWordPressでエラーメッセージが発生しました。
表題のエラーがPHP8.0より通知ではなく、独立エラーとして扱われるようになった為です
Contents
「get_the_terms」記事に基づくタクソノミーの取得のための下記の記述でエラー発生
<?php
$terms = get_the_terms($post->ID, 'tag-workshop');
if ($terms) {
foreach ($terms as $term) {
echo '<a class="tagc-tag-workshop" tag-id="' . $term->term_id . '" href="' . get_permalink() . '">' . $term->name . '</a>';
}
?>
Warning: Undefined variable $post in …
Warning: Attempt to read property “ID” on null in …
Undefined variable $postとは変数の未定義によるエラーです
▽global $post;を追記することによりエラー解消しました
<?php
global $post;
$terms = get_the_terms($post->ID, 'tag-workshop');
if ($terms) {
foreach ($terms as $term) {
echo '<a class="tagc-tag-workshop" tag-id="' . $term->term_id . '" href="' . get_permalink() . '">' . $term->name . '</a>';
}
?>
【PHP7から8へ切替】Warning: Undefined variableが表示された場合の修正方法
https://it-column.mjeinc.co.jp/archives/3513
WordPressのグローバル変数「$post」とは?中身や使い方について解説
https://tcd-theme.com/2024/01/wp-post-object.html
PHP8.0でWordPressの「Attempt to read property “ID” on null」のエラーを解決したい
https://ja.stackoverflow.com/questions/85387/php8-0%e3%81%a7wordpress%e3%81%ae-attempt-to-read-property-id-on-null-%e3%81%ae%e3%82%a8%e3%83%a9%e3%83%bc%e3%82%92%e8%a7%a3%e6%b1%ba%e3%81%97%e3%81%9f%e3%81%84