概要
現代の製造業におけるデジタル化の波は、従来の紙ベースの管理手法の限界を明らかにしています。特に現場作業者と管理職の間でのリアルタイムな情報共有と、場所にとらわれない生産管理の必要性が高まっています。本プロジェクトでは、これらの課題に対応するため、フロントエンドにVue.js、バックエンドにSpring Bootを採用した統合型製造現場管理プラットフォームを構築しました。
本システムは、管理者権限と一般作業者権限の2つのロールを用意。管理者はダッシュボード、従業員管理、設備情報の登録、生産計画の立案、工程管理などの機能を利用可能です。作業者側は、登録後に管理者が公開した生産指示書や設備情報、品質チェックリストを閲覧・実行できます。
バックエンドにはJava言語とSpring Bootフレームワークを採用し、データ永続化層にはMySQL 5.7を使用しています。これにより、高い拡張性と同時接続処理能力を確保し、ブラウザベースでシームレスな操作性を実現しています。
開発環境構成
- 実装言語:Java 11
- アプリケーションフレームワーク:Spring Boot 2.7.x
- ビルドツール:Maven 3.8+
- データベース:MySQL 5.7(InnoDBエンジン)
- フロントエンド:Vue.js 3.x
- 開発IDE:IntelliJ IDEA / VS Code
- アプリケーションサーバー:組み込みTomcat
- 推奨ブラウザ:Google Chrome 最新版
管理画面アクセスURL:http://localhost:8080/app-name/admin/index.html
初期管理者アカウント:admin/admin123
採用技術スタックの特徴
Java言語の利点
Javaは静的型付けのオブジェクト指向言語であり、堅牢性と移植性に優れています。マルチスレッド処理の豊富なAPIを備え、並行処理が必要な生産管理システムに最適です。さらに、豊富なオープンソースライブラリのエコシステムにより、複雑なビジネスロジックの実装を効率化できます。
例外処理機構とガベージコレクション機能により、システムの安定稼働を長期間維持できます。特にSpring Frameworkとの親和性が高く、エンタープライズアプリケーション開発のデファクトスタンダードとなっています。
Spring Bootの革新性
Spring Bootは、従来のSpringアプリケーションにおけるXML設定の煩雑さを解消するために誕生しました。自動設定(Auto-Configuration)機能により、開発者はビジネスロジックに集中できます。
スターターパターンを採用することで、必要な依存関係を簡潔に定義可能です。本システムではspring-boot-starter-web、spring-boot-starter-data-jpa、spring-boot-starter-securityなどを活用し、セキュアで効率的なRESTful APIを実装しています。プロダクション環境では、組み込みTomcatではなく外部Servletコンテナへのデプロイも柔軟に対応できます。
MySQLデータベースの選択理由
MySQLは、コミュニティ版が無償提供される高性能RDBMSです。ACID特性を完全にサポートし、製造業の厳格なデータ整合性要件を満たします。本システムでは、InnoDBストレージエンジンを採用し、トランザクション処理と行レベルロックを実現しています。
複雑なJOIN処理に対するクエリオプティマイザの性能が高く、設備情報や生産履歴の多テーブル検索において優れた応答速度を発揮します。また、バイナリログ機能によるポイントインタイムリカバリも、製造データの保全に重要です。
コア機能実装例
ファイル管理コントローラ
設備の画像や工程書類などのバイナリデータを管理するためのREST API実装例です。
package com.manufacturing.controller;
import java.io.File;
import java.io.IOException;
import java.util.Date;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource;
import org.springframework.core.io.UrlResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import com.manufacturing.service.SystemConfigService;
import com.manufacturing.common.R;
import com.manufacturing.entity.ConfigEntity;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import org.apache.commons.lang3.StringUtils;
@RestController
@RequestMapping("/api/resource")
public class ResourceController {
@Autowired
private SystemConfigService systemConfigService;
private static final String UPLOAD_DIR = "static/upload/";
@PostMapping("/upload")
public R uploadFile(@RequestParam("document") MultipartFile document,
@RequestParam(value = "category", required = false) String category) throws IOException {
if (document.isEmpty()) {
return R.error("アップロードファイルが空です");
}
String originalName = document.getOriginalFilename();
String extension = originalName.substring(originalName.lastIndexOf(".") + 1);
String timestamp = String.valueOf(new Date().getTime());
String storedName = timestamp + "_" + UUID.randomUUID().toString() + "." + extension;
java.nio.file.Path uploadPath = java.nio.file.Paths.get("src/main/resources", UPLOAD_DIR);
if (!java.nio.file.Files.exists(uploadPath)) {
java.nio.file.Files.createDirectories(uploadPath);
}
java.nio.file.Path targetPath = uploadPath.resolve(storedName);
document.transferTo(targetPath.toFile());
if ("profile".equals(category)) {
QueryWrapper<ConfigEntity> wrapper = new QueryWrapper<>();
wrapper.eq("config_key", "avatar_path");
ConfigEntity config = systemConfigService.getOne(wrapper);
if (config == null) {
config = new ConfigEntity();
config.setConfigKey("avatar_path");
}
config.setConfigValue(storedName);
systemConfigService.saveOrUpdate(config);
}
return R.ok().put("filename", storedName);
}
@GetMapping("/download/{filename}")
public ResponseEntity<Resource> downloadFile(@PathVariable String filename) throws IOException {
java.nio.file.Path filePath = java.nio.file.Paths.get("src/main/resources", UPLOAD_DIR).resolve(filename);
Resource resource = new UrlResource(filePath.toUri());
if (!resource.exists()) {
return ResponseEntity.notFound().build();
}
String contentType = "application/octet-stream";
return ResponseEntity.ok()
.contentType(MediaType.parseMediaType(contentType))
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + resource.getFilename() + "\"")
.body(resource);
}
}
製造指図コントローラ
生産計画と工程指示を管理するAPIの実装例です。スレッドセーフなID生成とロールベースアクセス制御を実装しています。
package com.manufacturing.controller;
import java.util.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*;
import com.manufacturing.service.ProductionOrderService;
import com.manufacturing.entity.ProductionOrderEntity;
import com.manufacturing.common.R;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
@RestController
@RequestMapping("/api/production")
public class ManufacturingOrderController {
@Autowired
private ProductionOrderService orderService;
@GetMapping("/order-list")
public R fetchOrderList(@RequestParam Map<String, Object> params,
@RequestParam(value = "status", required = false) String status,
HttpServletRequest request) {
Long operatorId = (Long) request.getSession().getAttribute("operatorId");
String role = (String) request.getSession().getAttribute("role");
QueryWrapper<ProductionOrderEntity> wrapper = new QueryWrapper<>();
if (!"ADMIN".equals(role)) {
wrapper.eq("assignee_id", operatorId);
}
if (StringUtils.isNotBlank(status)) {
wrapper.eq("production_status", status);
}
Page<ProductionOrderEntity> page = orderService.page(
new Page<>(Long.parseLong(params.get("page").toString()),
Long.parseLong(params.get("limit").toString())),
wrapper
);
return R.ok().put("orders", page.getRecords()).put("total", page.getTotal());
}
@PostMapping("/create")
public R generateOrder(@RequestBody ProductionOrderEntity order, HttpServletRequest request) {
Long creatorId = (Long) request.getSession().getAttribute("operatorId");
order.setOrderId(generateUniqueId());
order.setCreatorId(creatorId);
order.setCreatedAt(new Date());
orderService.save(order);
return R.ok().put("orderId", order.getOrderId());
}
@PutMapping("/modify")
@Transactional
public R updateOrder(@RequestBody ProductionOrderEntity order) {
orderService.updateById(order);
return R.ok();
}
@DeleteMapping("/remove")
public R deleteOrders(@RequestBody Long[] orderIds) {
orderService.removeByIds(Arrays.asList(orderIds));
return R.ok();
}
private Long generateUniqueId() {
return System.currentTimeMillis() + new Random().nextInt(999);
}
}
品質保証プロセス
テスト実施方針
本システムでは、テストピラミッドに基づいた多層的な品質保証アプローチを採用しています。単体テストにはJUnit 5とMockitoを、統合テストにはTestContainersを使用して実際のMySQLコンテナを起動させています。
結合テストでは、REST Assuredを用いたAPIエンドポイントの自動検証を実施。セキュリティテストとして、OWASP ZAPを導入し、SQLインジェクションやXSS脆弱性のスキャンを定期的に実行しています。
パフォーマンステストにはJMeterを活用し、同時接続200ユーザーを想定した負荷試験を実施。レスポンスタイムは99%のリクエストで500ms以内を基準値として設定しています。
まとめ
本製造現場管理システムは、モダンな技術スタックを活用することで、従来の生産管理システムの課題を解決しています。Vue.jsによる直感的なUIと、Spring Bootの堅牢なバックエンド処理により、現場作業者の生産性向上を実現しました。
今後の展望として、機械学習を用いた設備異常予測機能の追加や、IoTデバイスとの連携によるリアルタイム生産進捗監視機能の実装を計画しています。また、マイクロサービスアーキテクチャへの移行も検討中で、より柔軟な機能拡張とスケーラビリティの向上を目指しています。