はじめに
未経験からエンジニアを目指し、転職活動にあたって作成したポートフォリオについてまとめます。
アプリの概要
このアプリは、商品画像をアップロードすると
🔹 画像から自動でタグを抽出
🔹 AI で商品説明文を生成
をしてくれるWeb アプリケーションです。
ユーザー登録から商品一覧表示・編集まで一通りの CRUD を実装しています。
主な機能
-
ユーザー登録・ログイン
-
商品情報の登録・一覧表示・編集
-
画像アップロード → タグ抽出(Google Vision API)
-
説明文自動生成(Gemini API)
-
Render へのデプロイ(無料プランが切れたので閉鎖中)
登録の流れ

商品の画像・商品名・価格・数量を入力し、「説明文生成ボタン」をクリックします。

AIにより商品の説明文が生成されます。必要に応じて文章を編集し、保存ボタンを押します。
技術スタックと選定理由
| 層 | 技術 | 理由 |
|---|---|---|
| バックエンド | Java 17 + Spring Boot | Java の基礎を深めたかった |
| 認証 | Spring Security | 標準的なユーザー管理を習得 |
| DB | PostgreSQL | 実運用に近い RDB を経験 |
| フロント | HTML/CSS + JavaScript + Vue.js(一部) | SPA とは別の組み合わせを学ぶ |
| 画像解析 | Google Vision API | 実データからタグを抽出 |
| 説明文生成 | Gemini API | LLM を活かしたナチュラルなテキスト生成 |
| デプロイ | Render |
選択基準は「現場でよく使われる技術を、自分で使いこなせるようにすること」でした。
テーブルレイアウト
AIの生成履歴を保持するため、AI説明文テーブルにはAIが生成したそのままの文章(content)と、それに人が手を加え完成した説明文(edited_description)をそれぞれ保存する仕様にしています。
説明文生成の仕組みをざっくり解説
ProductControllerが入力された商品情報を受け取り、ProductServiceに情報を渡します。
@PostMapping("/productcreate")
public ResponseEntity<PostResult> handlePostAndReturnJson(
@RequestParam("productimage") MultipartFile file ,
@RequestParam String name,
@RequestParam int price,
@RequestParam int stock) throws IOException {
if(!file.isEmpty()) {
//画像・コメント保存とDB登録の処理をサービスへ移譲
System.out.println("saveProduct呼び出し");
PostResult result = productService.saveProduct(file,name,price,stock);
ProductServiceが受け取った情報をVision APIに渡し、返ってきたタグをもとにプロンプトを作成します。
さらに作成したプロンプトをGemini APIに渡し、説明文が生成されます。
public PostResult saveProduct(MultipartFile file, String name, int price, int stock) throws IOException
//画像を保存(リサイズ+圧縮)
//認証ユーザーの取得
//画像、商品名、価格、在庫を非公開状態で一時保存
//画像解析
byte[] imageBytes = file.getBytes();
String fullUrl = "http://localhost:8080/uploads/" + filename;
List<String> tags = visionService.extractLabels(imageBytes);
System.out.println("抽出されたタグ" + tags);
//プロンプト作成
String prompt = promptService.buildPrompt(tags,name);
System.out.println("受け取ったプロンプト:" + prompt);
//AIメッセージ生成
String discription = geminiService.callGeminiApi(prompt);
System.out.println("受け取った説明文:" + discription);
//AIメッセージをDBに保存
return new PostResult(discription,aiDescription.getId());
}
Vue.jsについて
フロントエンドはThymeleafを使って実装していましたが、以下の場面で非同期処理の必要性を感じました。
- 商品登録時に、「商品情報を入れて説明文生成ボタンを押す→画面遷移して説明文が表示される」だと流れがスムーズではない
- 商品一覧の検索時に結果を即時表示したい
アプリ全体をSPA化することは考えていなかったので、Vue.js 3のOptional APIをCDNで利用しました。
JavaScriptの知識があまりない状態でしたが公式チュートリアルが分かりやすかったため、なんとか使うことができました。
以下は説明文生成ボタンを押した際の処理です。ローディング表示やバリデーション、エラーハンドリングを実装しています。
async generateDescription() {
this.loading = true;
this.showGenerateModal = false;
console.log("画像ファイル:", this.imageFile);
// バリデーションチェック
if(!this.isValidProductInput()) {
this.showGenerateModal = false;
await this.$nextTick();
alert("商品名・画像・価格・在庫数を正しく入力してください(半角整数・0以上)")
this.loading = false;
return;
}
// FormDataの作成
const formData = new FormData();
formData.append('name',this.name);
formData.append('price',this.price);
formData.append('stock',this.stock);
formData.append('productimage',this.imageFile);
try {
// バックエンドへPOSTリクエスト
const response = await axios.post('/api/product/productcreate',formData)
this.aiDescriptionId = response.data.aiDescriptionId;
this.aiDescription = response.data.aiDescription;
this.isLocked = true;
console.log("反映された説明文:", this.aiDescription);
this.isLocked_generatebefore = false;
} catch(error) {
console.error("説明文生成失敗",error);
alert("説明文の生成に失敗しました");
} finally {
this.loading = false;
}
},
大変だった点
知識がなさすぎる
Javaの基礎を『スッキリわかるJava』で学びたての初心者で、Webの仕組みやDBとの接続、フロントエンドとバックエンドのやり取り、MVC構成など、明らかに知識が足りていませんでした。
引き返して座学に戻るかと悩みましたが、一から本を読んで学ぶよりも、Webアプリの開発を通して必要な知識を適宜身に着けていく学習スタイルが自分には合っていたと感じます。
〈学習に使った教材〉
書籍
- 『スッキリわかるJava入門』- Java の基礎文法を学習
- 『[改訂2版]プロになるためのWeb技術入門』- Web の仕組み、HTTP、サーバーサイドの基礎を理解
- 『プロになるSpringBoot入門』- Spring Boot でのアプリケーション開発を学習
オンライン教材・ドキュメント
- Vue.js 3 公式チュートリアル - Options API の基本
- Spring Security 公式ドキュメント - 認証・認可の実装
- Google Cloud Vision API ドキュメント
- Gemini API ドキュメント
開発中に参考にした情報源
- Stack Overflow
- Qiita の Spring Boot 関連記事
これからについて
今年SES企業に入社し、開発案件に参加できることになりました。
このポートフォリオの改善点がたくさん見えてくると思うので、身に着けた技術で改良していきたいと思います。
関連記事
課題の整理を行なっています。

